REBOL

REBOL Programming For The Absolute Beginner

By: Nick Antonaccio
Updated: 3-1-09
----------------------
68 YouTube video tutorials that follow this text (10 hours of video) are available here.
A MORE CONCISE version of this tutorial is available here (practical fundamentals - just the facts).
For an even shorter introduction, try this program for children (it's also suitable for adults who want a simple quick-start).

Contents:

1. For New Programmers and for New Rebolers
2. Some Perspective for Beginners
3. Using the REBOL Interpreter to Speak to the Computer - "Hello World"
3.1 Five Short Examples
4. First Code Examples - Creating a GUI Window
5. More Examples
6. A Quick Comparison
7. Understanding Variables and Functions
8. REBOL Words
9. Before You Get Too Far - Learn by Doing
10. GUI Words and Grammar - Some More Depth
10.1 Actions
11. Creating Your Own Variable Words
12. Blocks and Series
13. Function Words
14. Several Ways to Create Functions in REBOL - Passing Variables
15. Conditional Operations
16. Looping
17. Working With Longer Examples
17.1 "Compiling" REBOL Programs - Distributing Packaged .EXE Files:
18. Embedding Binary Data
18.1 Compression
19. Modular Programming and Code Reuse
19.1 Using External Programs as "Modules":
20. A Quick Summary of the REBOL Language
20.1 Using Rebol's Built-In Help
21. 8 Complete REBOL Programs For You To Study
21.1 Little Email Client
21.2 FTP Chat Room
21.3 Looping Through Data
21.4 Image Effector
21.5 Sliding Tile Game
21.6 Guitar Chord Diagram Maker
21.7 Listview Database
21.8 Peer-to-Peer Instant Messenger
22. Menus
23. More About GUIs and Graphics
23.1 Responding to Special Events in a GUI - "feel"
23.2 2D Drawing, Graphics, and Animation
23.3 Using Animated GIF Images
23.4 3D Graphics with r3D
24. Multitasking
25. DLLs and Shared Code Files
25.1 What They are and Why to Learn About Them
25.2 How To Use Them
26. Understanding the CGI Interface and Web Programming with REBOL
27. Database Concepts - Using MySQL
27.1 Setting up and using MySQL
27.2 Tables and SQL statements
27.3 Using SQL Statements in REBOL code
27.4 Putting It All Together
27.5 Short Format
28. Parsing
29. REBOL Objects
30. Common Errors
30.1 Trapping Errors
31. How to Organize Your Coding Thought - Some Real World Examples
31.1 Using Outlines and Pseudo Code
31.2 Case 1 - Scheduling Teachers
31.3 Case 2 - Downloading Directories
31.4 Case 3 - Creating a Simple Calculator Application
31.5 Case 4 - Creating a Tetris Game Clone
31.6 Case 5 - Coding a Freecell Game Clone (GUI)
31.7 Case 6 - Vegetable Gardening
31.8 Case 7 - Scheduling Teachers, Part Two
31.9 Case 8 - An Additional Teacher Automation Project
31.10 Case 9 - An Online Member Page CGI Program
31.11 Case 10 - A CGI Event Calendar
31.12 Case 11 - A Backup Music Generator (Chord Accompaniment Player)
31.13 Case 12 - A Simple Image Gallery CGI Program
32. Other Topics
32.1 6 REBOL Flavors
32.2 Spread the Word: 7 Reasons to Learn and Use Rebol
33. What Next?
33.1 A Final Point
34. Feedback

1. For New Programmers and for New Rebolers

This tutorial demonstrates how easy it is to accomplish real world programming goals with a flexible and powerful language called REBOL. The text aims to teach average users to program computers to do useful things, without the long and difficult learning curve imposed by other programming languages. If you're an experienced programmer, it's strongly recommended that you read this more concise tutorial. You'll be amazed at Rebol's compact code and simple cross-platform usability. If you're a beginner, you won't find another tutorial, book, or language that enables you to accomplish more useful programming tasks within a few hundred pages. REBOL is more productive than anything else, anywhere.

A downloadable package of 11 code examples from this tutorial is available at:

http://musiclessonz.com/rebol_tutorial_examples.zip

The zip file contains screen shots and executables demonstrating what can be easily accomplished by a casual, part-time REBOL coder, within several weeks of study. Here are a few examples:

A YouTube video version of this tutorial is available at http://musiclessonz.com/rebol_video_links.html

The concepts demonstrated throughout the tutorial will help new programmers understand other languages and programming tools, and will lay the groundwork for novices to acquire essential coding skills. Even novices will be able to actually create useful REBOL programs in the first few days. And REBOL isn't limited to certain types of applications or operating systems. Code written in REBOL is able to run unchanged on over 40 operating systems, and can be used to build an enormous variety of user applications with modern graphics, CGI interfaces, network functionality, database connectivity, and much more. If you're used to other development environments, you'll find that REBOL is a beautifully designed language, contained in a dramatically small and efficient package that elegantly replaces many other mainstream tools. Learning REBOL is productive, enjoyable, and thought provoking for both new and experienced coders. No matter what your experience level, learning REBOL will save you time and frustration at some point in your programming endeavors. It's a fantastically versatile little tool for which you'll find many uses.

2. Some Perspective for Beginners

Before diving into raw mechanics, here's a fundamental perspective that's helpful to understand:

Modern computers, operating systems and programs all do very basic things, in limited ways, with a limited scope of data types:

  1. They let users input various types of data: text, images, sounds, video, etc.
  2. They let users save, retrieve, organize, share/transfer, manipulate, alter, view and otherwise deal with that data in useful ways.

Everything that can be done with a modern computer basically involves manipulating text and non-text data (non-text data is called "binary" data). In the current state of modern computing, data of all types is typically input, manipulated, and returned via graphical user interfaces such as Windows program interfaces, web forms displayed in browsers, and other keyboard/mouse driven "GUI"s. Data is saved on local hard drives and storage devices (CDs, thumb drives, etc.) and on remote web servers, and is typically transferred via local networks and Internet connections. Knowing how to control those familiar computing elements to allow users to manipulate data, is the goal of learning to program. It doesn't matter whether you're interested in writing business applications to work with inventory and scheduling (text data), programs to alter Internet web pages or emails (text and image data), programs to organize or play music (binary data), programs to transfer files across networks (text and/or binary data), programs to broadcast video and sound across the Internet (rapidly transferred sequential frames of binary data), programs to control robotic equipment, programs to play games, etc... They all require learning to input, manipulate, and return data of some sort. You can do all those things with REBOL, and once you've done it in one language, it's easier to do with other specialized languages and programming tools.

REBOL handles common user interfaces and data types easily and intuitively. It allows programmers to quickly build graphic interfaces to input and return all common types of data. It can easily manipulate text, graphics, and sounds in useful ways, and it provides simple methods to save, retrieve, and share data across all types of hardware, networks, and the Internet. That makes it a great way to begin learning how to program. For more information about the useful qualities of REBOL, see http://musiclessonz.com/rebol.html#section-1.

3. Using the REBOL Interpreter to Speak to the Computer - "Hello World"

The REBOL interpreter is a program that runs on your computer. It translates written text organized in the REBOL language syntax ("source code") to instructions the computer understands. One of the great things about REBOL is that it's a very small program, contained in a single file. There are versions that run on just about every type of computer and operating system (Windows PC, Macintosh computer, Linux web server, etc.), without any complicated install process. It lets you speak to all those machines using the same language. You just download the REBOL interpreter program, feed it a text file full of code, and the machine does what you want, with the data you want. It's easy to use and only about 1/2 meg to download (less than a minute even on a slow dialup connection).

To get the free REBOL interpreter for Microsoft Windows, go to http://rebol.com/view-platforms.html and download the view.exe file for Windows - it's clearly marked, just click it with your mouse and save it to your hard drive. When you run view.exe for the first time, you can install it if you want, but you don't have to. Just follow the instructions on screen. Once you've got the REBOL interpreter downloaded and running on your computer, click the "Console" icon, and you're ready to start typing in REBOL programs. To create your first program, type the following line into the REBOL interpreter, and then press the [Enter] (return) key on your keyboard:

alert "Hello world!"

Before going any further, give it a try. Download REBOL, and type in the code above to see how it works. It's simple and it only takes a moment. If you want to run REBOL on any other operating system, just select, download and run the correct file for your computer. It works the same way on every operating system.

3.1 Five Short Examples

To whet your appetite, here are 5 tiny GUI programs that demonstrate just how potent REBOL code is. The first example is a fully functional web page editor. You can use it to edit html pages and other text files on any web site ftp server:

view layout [
    h1 "Enter your ftp info, then click 'load' to download and edit a file:"
    p: field 600 "ftp://user:pass@website.com/public_html/filename.html"
    h: area 600x440 across 
    btn "Load" [h/text: read (to-url p/text) show h]
    btn "Save" [write (to-url p/text) h/text]
]

Here's a classic graphic sliding tile game:

view center-face layout [
    origin 0x0 space 0x0 across 
    style p button 60x60 [
        if not find [0x60 60x0 0x-60 -60x0] 
            face/offset - empty/offset [exit]
        temp: face/offset face/offset: empty/offset 
            empty/offset: temp
    ]
    p "A" p "B" p "C" p "D" return p "E" p "F" p "G" p "H" return
    p "I" p "J" p "K" p "L" return p "M" p "N" p "O"  
    empty: p 200.200.200 edge [size: 0]
]

Here's a little painting program:

view layout [
    h1 "Paint with the mouse:"
    scrn: box black 400x400 feel [
        engage: func [face action event] [
            if find [down over] action [
                append scrn/effect/draw event/offset show scrn
            ]
            if action = 'up [append scrn/effect/draw 'line]
        ]
    ] effect [draw [line]]
    btn "Save" [
        save/png %/c/painting.png to-image layout [
            origin 0x0 box black 400x400 effect pick get scrn 9
        ] alert "Saved to C:\painting.png"
    ]
    btn "Clear" [scrn/effect/draw: copy [line] show scrn]
]

Here's an even more compact version of the above program:

view layout[h1 "Paint:" s: area 700x500 feel[engage: func[f a e][
if a = 'over[append s/effect/draw e/offset show s]if a = 'up[
append s/effect/draw 'line]]]effect[draw[line]]b: btn "Save Image"[
save/png %a.png to-image s alert "Saved to 'a.png'"]btn "Clear"[
s/effect/draw: copy [line] show s]]

And just to take that example to the limit, here's the smallest painting program you'll ever see, in any programming language:

view layout[s: area feel[engage: func[f a e][if a = 'over[append 
s/effect/draw e/offset show s]]]effect[draw[line]]]

Here's a short program that uses Rebol's parsing and networking abilities to display the current WAN and LAN IP addresses of your computer:

parse read http://whatsmyip.org/ [thru <title> copy my-ip to </title>]
parse my-ip [thru "Your IP is " copy stripped-ip to end]
alert to-string rejoin ["WAN: " stripped-ip " ---- LAN: "
    read join dns:// read dns://]

Here's a little email client you can use to read and send emails to/from any pop/smtp server (edit the first line so it contains your personal email account info):

set-net [user:pass@website.com smtp.website.com pop.website.com]
view layout[
    h1 "Send:" a: field "user@website.com" s: field "Subject" b: area
    btn "Send"[
        send/subject to-email a/text b/text s/text alert "Sent"
    ]
    h1 "Read:" m: field "pop://user:pass@website.com"
    btn "Read"[editor read to-url m/text]
]

Try pasting those examples into the REBOL interpreter to see what just a little REBOL code can do (those programs take up a total of less than 1 printed page of code). Before the end of this tutorial you'll know exactly how they all work, and much more...

4. First Code Examples - Creating a GUI Window

Now for some code! Computer programs typically use Graphical User Interfaces ("GUI"s) to get data from the user and to display data to the user. GUIs work intuitively, and modern computer users are familiar with them. They contain clickable buttons, text entry fields, menus, images, and other "widgets" that allow the user to interact with the computer. Windows programs are GUIs. Web pages are also GUIs. Users click buttons with the mouse pointer to perform actions, select settings from menus, type text data into fields, etc. Most modern programming languages include some facility to build graphic interfaces that can be used to interact with the user. REBOL makes GUI creation easier than any other language. To create a simple GUI window, just type the following line into the REBOL interpreter, and press [ENTER]. Notice the "view layout" words in the examples below - you'll use them every time you create a GUI interface in Rebol:

view layout/size [] 400x300

That line of code creates a window 400 pixels across and 300 pixels down (pixels are dots on the computer screen). It doesn't do anything yet, but the GUI window can be moved around the screen, minimized and closed (with the "X" in the upper right hand corner), just like any other Windows program. In other programming languages, just creating a window like that can take several pages of code and lots of preliminary understanding. REBOL makes it easy.

To add a button to the above GUI, type the following code. Notice that the word "button" has been added between the brackets:

view layout/size [button] 400x300

Now the GUI has a generic blue button that you can click with the mouse. To add some text to the button, type the following code. Notice that the text "Click Me" has been added after the button:

view layout/size [button "Click Me"] 400x300

To make the button do something, type the following code, and then click the GUI button with your mouse pointer. Notice that the text [alert "Clicked!"] has been added after the button text:

view layout/size [button "Click Me" [alert "Clicked!"]] 400x300

Now when you click the button in your GUI, the computer responds by alerting you with the message "Clicked!". To make the program do something a bit more interactive, type the following, and then click the GUI button once again. Notice the "data: request-text" addition at the beginning of the line. That code requests some text from the user and assigns it to the word "data", so it can be referred to and used in the program:

data: request-text view layout/size [
    button "Click Me" [alert data]] 400x300

The code above is split onto two lines so that it fits within the width of this web page, but it can be typed into the REBOL interpreter as a single line. That one line is all it takes to create a program which gets some data from the user, creates a graphic user interface that waits for user interaction, and does something with the input data (displays it in a little dialog box).

Next, we'll save some data to your computer's hard drive. Type in the following code, and click "yes" if the REBOL interpreter asks for your permission to write to the hard drive. Notice the "write %/c/data.txt data" added to the end of the line. That code writes the data collected from the user, to a file on the C: drive called "data.txt"

data: request-text view layout/size [
    button "Click Me" [alert data]] 400x300 write %/c/data.txt data

If you look on your computer's C: drive after closing the GUI, you'll see that there now exists a text file (C:\data.txt) containing the text you typed into the program. (If you're working in an operating system other than Windows, you'll need to change the "c" character in the above line to refer to a root directory on your hard drive).

With that one line of code, you've got a working program that actually does something useful. It gets, displays, and saves some data from a user, using familiar GUI interactions. You could adjust it to store phone numbers, usernames/passwords, or any other useful information. It's easy - and things only get more interesting from there!

REBOL is great at dealing with all types of common data - not just text. You can easily display photos and other graphics in your GUIs, play sounds, display web pages, etc. And it's just as good at dealing with data transferred across networks and the Internet. Here's some code that downloads an image from a web server and displays it in a GUI - notice the "view layout" words again:

view layout [image load http://rebol.com/view/bay.jpg]

The word "image" inside the brackets displays a picture in the GUI. The word "load" downloads the image to be displayed.

REBOL allows many built-in effects to be applied to images. Notice the "effect [effect type]" code in the following examples:

view layout [image load http://rebol.com/view/bay.jpg effect [Grayscale]]
view layout [image load http://rebol.com/view/bay.jpg effect [Emboss]]
view layout [image load http://rebol.com/view/bay.jpg effect [Flip 1x1]]

Here's how you can read that same file from the web server and save it to your C: drive. The "/binary" modifier is used whenever reading or writing binary (non-text) data:

write/binary %/c/bay.jpg read/binary http://rebol.com/view/bay.jpg

Now you can read the image directly from your hard drive and display it in a GUI. Just type in the code below. Notice the "view layout" words again, and this time in between the brackets, the file is loaded from the local C: drive:

view layout [image load %/c/bay.jpg]

It's important to note here that you could also use that image in other graphic applications on your computer - including other programs that aren't written in REBOL. You could, for example, open that file in an image editing program, or attach it to an email and send it to a friend. It's critical to understand that data can be interoperable between languages, programming tools, operating systems, and other connected domains. As you learn more about programming, you may find specialized tools that accomplish certain programming goals easily and effectively. You can trade data between various tools by just saving it to a shared storage medium. In that way, all programming languages and tools can be used together to accomplish complex goals. That's a key concept to keep in mind when learning about general programming. Very little technology is truly new. The basis for most computing applications has been around for several decades (hard drives for storing files within directory structures, hardware for inputting and outputting text, graphic, audio, and other data, networks for transferring data between machines, etc.). Those base components haven't changed too dramatically. They've simply improved in speed and capacity - and the software tools used to work with them have evolved to allow programmers to do high level things more quickly and easily. An adept programmer writing an application to deal with large amounts of text data shared across the Internet, however, will realize that the data all still resides in files on a hard drive somewhere, in a machine connected to a network, and that data can be accessed by various old fashioned programming means - even if it was created and put there by a program using cutting edge database technology, displayed in a flashy new graphic interface, and transferred over broadband wireless connections. It's still just text data saved in a file somewhere, and it can be manipulated via virtually any language that provides access to networks and text files! Keep that in mind as your exposure to various programming tools expands...

5. More Examples

Below are some more short examples of reading, writing, and manipulating data on the hard drive and Internet, and interacting with the user. Type them into the REBOL interpreter to familiarize yourself with a bit more of the REBOL language.

Before going on, you may want to configure REBOL to open straight to the interpreter console. Click the "User" menu on the REBOL Viewtop, and uncheck "Open Desktop On Startup". That'll save you the trouble of clicking the "Console" button every time you start REBOL.

The following line displays the current day and time in the interpreter:

print now

The word "print" displays text data directly in the REBOL interpreter. The word "now" refers to the current date and time.

The following line performs some mathematical computations, and displays the result:

print (10 + 12) / 2

The following code asks the user to choose a file on the hard drive:

request-file

The code below allows the user to choose a color:

request-color

The following code asks the user a yes-no question:

request "Are you having fun yet?"

Here's a nice way to let the user select a date:

request-date

The code below requests a username and password from the user:

request-pass

The following code opens your computer's web browser and displays the indicated web page:

browse http://rebol.com

The following code launches Rebol's built in text editor, and opens the file c:\test.txt

editor %/c/test.txt

Notice the percent character ("%") in the example above. In REBOL, that character is used to represent all file labels. Because REBOL can be used on many operating systems, and because those operating systems all use different syntax formats to refer to drives, paths, etc., REBOL uses the universal format: %/drive/path/path/.../file.ext . For example, "%/c/windows/notepad.exe" refers to "C:\Windows\Notepad.exe" in Windows. REBOL converts that syntax to the appropriate operating system format, so that your code can be written once and used on every operating system, without alteration.

The following line sends an email to user@website.com, containing the text "Hi user. How are you doing?". Try replacing the username and website with your own email address (If you downloaded and ran REBOL without actually installing it, you'll need to run the configuration wizard in order to send the email. To do that, type "install" and follow the instructions.):

send user@website.com "Hi user.  How are you doing?"

The line below sends a web page to user:

send user@website.com read http://www.rebol.com

The code below displays the contents of user's email inbox:

print read pop://user:pass@website.com

The following line uploads a single file to user's web server using ftp:

write/binary ftp://user:pass@website.com read/binary %file

The following uploads an entire directory of files to user's web server:

foreach file load %./ [if not dir? file [write/binary join
    ftp://user:pass@website.com/ file read/binary file]]

It all looks a lot like spoken English, doesn't it? You just need to type things in correctly, and the computer does what you want. The more of the language you learn, the more you'll be able to make the computer do your bidding... Easy, right?

6. A Quick Comparison

To provide a quick idea of how much easier REBOL is than other languages, here's a short example. The simplest code to create a basic REBOL GUI window was presented earlier:

view layout/size [] 400x300

It works on every type of computer, in exactly the same way.

Code for the same simple example is presented below in the popular programming language "C++". It does the exact same thing as the REBOL one-liner above, except it only works in Microsoft Windows. If you want to do the same thing on a Macintosh computer, you need to memorize a completely different page of C++ code. The same is true for Unix, Linux, Beos, or any other operating system. You have to learn enormous chunks of code to do very simple things, and those chunks of code are different for every type of computer. Furthermore, you typically need to spend a semester's worth of time learning very basic things about coding format and fundamentals about how a computer thinks before you even begin to tackle useful basics like the code below:

#include <windows.h>

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
char szClassName[ ] = "C_Example";

int WINAPI
WinMain (HINSTANCE hThisInstance,
         HINSTANCE hPrevInstance,
         LPSTR lpszArgument,
         int nFunsterStil)

{
    HWND hwnd;               
    /* This is the handle for our window */
    MSG messages;            
    /* Here messages to the application are saved */
    WNDCLASSEX wincl;        
    /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      
    /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 
    /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 
    /* No menu */
    wincl.cbClsExtra = 0;                      
    /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      
    /* structure or the window instance */
    /* Use Windows's default color as window background */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register window class. If it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
           0,                   
            /* Extended possibilites for variation */
           szClassName,         
            /* Classname */
           "C_Example",       
            /* Title Text */
           WS_OVERLAPPEDWINDOW, 
            /* default window */
           CW_USEDEFAULT,       
            /* Windows decides the position */
           CW_USEDEFAULT,       
            /* where the window ends up on the screen */
           400,                 
            /* The programs width */
           300,                 
            /* and height in pixels */
           HWND_DESKTOP,        
            /* The window is a child-window to desktop */
           NULL,                
            /* No menu */
           hThisInstance,       
            /* Program Instance handler */
           NULL                
            /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nFunsterStil);

    /* Run the message loop. 
        It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages 
            into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - 
        The value that PostQuitMessage() gave */
    return messages.wParam;
}
/*  This function is called by the Windows 
        function DispatchMessage()  */

LRESULT CALLBACK
WindowProcedure (HWND hwnd, UINT message, 
    WPARAM wParam, LPARAM lParam)
{
    switch (message)                  
    /* handle the messages */
    {
        case WM_DESTROY:
            PostQuitMessage (0);       
                /* send a WM_QUIT to the message queue */
            break;
        default:                      
            /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, 
                wParam, lParam);
    }

    return 0;
}

Yuck. Back to REBOL...

7. Understanding Variables and Functions

All programming languages make use of two important features: variables and functions. Functions are commands that tell the computer to do something. They're similar to verbs in spoken language. You've used some functions already in the earlier examples. For example, "print" is a function - it can be thought of as a verb that represents some action.

Variables are names given to data to be input, stored, manipulated, displayed, etc. They're similar to nouns in spoken language. For example, "now" can be thought of as a variable. It's a noun referring to a piece of data (the current time).

Functions and variables must be used in a syntax defined by the programming language you're using. No existing computer language allows you to speak freely as in the natural language sentence "show the user some photos of their children". You have to write the commands in a more specific way - using a syntax that invariably involves variables and functions. As a programmer, to get the computer to "show the user a photo", you'd:

  1. assign the picture filename to a variable (noun)
  2. use a function (verb) to display the image referred to by the variable
  3. assign the function action to a button or some other graphic widget in a GUI (which is itself created by arranging variables and functions in a specific order)

REBOL does many things in as "high level" a way as can be expected - as close to speaking to the computer in human English as is currently possible, but it still requires specific syntax and structure. All contemporary computer languages do. If you want to learn how to program in any language, you'll need to learn how to use variables and functions in the grammar the language defines. That's at the core of all programming.

In REBOL, variables and functions are assigned to "words".

8. REBOL Words

If you want to give a label to some data - so that it can be used in your program - you must assign it a word. The same is true for actions performed by functions. There are many function words built into REBOL that represent common actions ("print", "alert", "request-date", etc). You've seen a number of those built-in words already in the earlier examples. To create your own functions, you take previously created function words and variables, group them together in a specific order that accomplishes what you want, and assign a word to that collection of code. Then you can refer to that group of actions and/or related data, using the word you've assigned to it.

Learning the function words that are already defined in a programming language is a big part of learning to program. Those words are the existing vocabulary of the language, and in order to work correctly, they typically expect some other words to follow afterwards, in a specific order and format. The word "write", for example, writes data to a storage device (hard drive, flash drive, web server, etc). It's a function that performs an action, and expects the name of a file to be written to the hard drive, and then the name of the data to write to that file. It's gotta be written in that order:

write %/c/text.txt "This is some random text"

If you type the above line in the wrong order, it won't work:

write "This is some random text" %/c/text.txt ; WRONG

If you type it in backwards, REBOL won't understand the syntax, and you'll receive an error.

9. Before You Get Too Far - Learn by Doing

To learn any programming language, you'll need to type in lots of code, by rote. Be sure to manually key in the examples in this tutorial, not just to see them work, but to get used to speaking the language. Avoid copying and pasting if you really want to learn. At first, you'll learn to code in the same way you'd learn a new spoken language - by immersing yourself in the words and phrases of the language (even if at first you don't understand what's being said). Repeating sentences by rote is a natural way to get a feel for how a language sounds and works. Certain words, phrases and patterns eventually become familiar, and the language syntax becomes clear and more intuitive.

Seeing the examples run is absolutely essential. It helps form associations between things the computer does, and chunks of code that make those things occur. Just reading through the code isn't enough - seeing how it executes is the key. If you don't have time to manually type in code, at very least paste it into the interpreter.

Because programming languages require more specific syntax than spoken languages, learning the exact grammar is more important than in spoken language. Simple examples like the ones that follow are easy to remember - just like simple phrases memorized in spoken foreign languages. To become more fluent, however, the correct order of words, characters and other elements must be typed in exactly, in a much more specific way than components of spoken language. The interpreter is not intelligent enough to "guess" what you intend to do, and if you get the grammar wrong (even one character), it'll interpret your code incorrectly and do something different then you want. Sometimes, you'll get an error, and the interpreter will tell you where you got the grammar wrong in your code. Sometimes your program just won't work correctly. Getting intimately familiar with the syntax, from the beginning, will help you avoid those problems.

10. GUI Words and Grammar - Some More Depth

As you saw in the earlier examples, the words "view layout", followed by two brackets ("[]") can be used to display a Graphic User Interface in REBOL. You can put elements that you want to see in the GUI, inside the brackets. REBOL contains words that display all the common graphic elements used in GUIs. Try typing in the following code examples:

view layout [button]

view layout [field]

view layout [text "REBOL is really pretty easy to program"]

view layout [text-list]

view layout [
    button
    field
    text "REBOL is really pretty easy to program"
    text-list
    check
]

Notice that the words can be separated by "white space" inside the brackets. Extra spaces, carriage returns, and other empty characters are ignored by the interpreter. Tab stops are traditionally used to indent the lines within brackets, but they're not required.

More descriptive characteristics about the graphic elements can be included directly after each of their respective words. Such modifiers are called "facets" in REBOL, and they allow you to adjust all characteristics of every type of graphic widget (size, color, displayed text), etc. Try typing in the code below - it's the same as the above code, with some additional facet characteristics:

view layout [
    button red "Click Me"
    field "Enter some text here"
    text "REBOL is really pretty easy to program" purple
    text-list 400x300 "line 1" "line 2" "another line"
    check yellow
]

10.1 Actions

IMPORTANT: If you want a graphic element to perform an action, just put the action word(s) in brackets after it. When the GUI element is clicked with the mouse or otherwise activated, the action will be performed. Type in the following code to see how it works:

view layout [button [alert "You clicked the button."] ]

view layout [button red "Click Me" [alert "You clicked the red button."]]

view layout [
    text "Some action examples.  Try using each widget:"
    button red "Click Me" [alert "You clicked the red button."]
    field 400 "Type some text here, then press [Enter] on your keyboard."
        [alert value]
    text-list 400x300 "Select this line" "Then this line" "Now this line"
        [alert value]
    check yellow [alert "You clicked the yellow check box."]
    button "Quit" [quit]    
]

Notice how the word "value" refers to the selected item in the text-list, and to the text contained in the text input field.

Alternate actions (i.e., those triggered by a right click of the mouse) can be included for any graphic widget. Just enclose them in a second block, surrounded by brackets:

view layout [button [alert "left click"] [alert "right click"]]

Here are some other GUI elements used in the REBOL language:

view layout [
    backcolor white
    h1 "More GUI Examples:"
    box red 500x2
    bar: progress
    slider 200x16 [bar/data: value show bar]
    area "Type here"
    drop-down
    across 
    toggle "Click" "Here" [print value]
    rotary "Click" "Again" "And Again" [print value]
    choice "Choose" "Item 1" "Item 2" "Item 3" [print value]
    radio radio radio
    led
    arrow
    return
    text "Normal"
    text "Bold" bold
    text "Italic" italic
    text "Underline" underline
    text "Bold italic underline" bold italic underline
    text "Serif style text" font-name font-serif
    text "Spaced text" font [space: 5x0]
    return
    h1 "Heading 1"
    h2 "Heading 2"
    h3 "Heading 3"
    h4 "Heading 4"
    tt "Typewriter text"
    code "Code text"
    below
    text "Big" font-size 32
    title "Centered title" 200
    across
    vtext "Normal"
    vtext "Bold" bold
    vtext "Italic" italic
    vtext "Underline" underline
    vtext "Bold italic underline" bold italic underline
    vtext "Serif style text" font-name font-serif
    vtext "Spaced text" font [space: 5x0]
    return
    vh1 "Video Heading 1"
    vh2 "Video Heading 2"
    vh3 "Video Heading 3"
    vh4 "Video Heading 3"
    label "Label"
    below
    vtext "Big" font-size 32
    banner "Banner" 200
]

The examples above demonstrate how REBOL creates GUIs that can be used to input and display data in a variety of ways that are familiar to users. They can be customized using facets, and they can perform actions. That's a big part of building a typical modern computer program! For more information about GUI design, see http://rebol.com/docs/easy-vid.html and http://rebol.com/docs/view-guide.html. To make your GUIs do useful things, you need to learn more about making the language manipulate data in useful ways...

11. Creating Your Own Variable Words

Just like spoken languages, programming languages are malleable and expressive. There's never only one way to write a given program. You need to choose and use your own words, and you need to organize them so they have the meaning and function you intend. Just like in spoken language, you have to think about what you're trying to say, and organize your thought process to say it. In REBOL you have the additional benefit of being able to create your own language words to express actions and to label data.

Words are created and assigned to variables and functions in REBOL by the use of the colon (":") character. You can use any word you want to refer to any specific data or action(s). For example, If you want to use the word "picture" to refer to an image file on the Internet, you could do the following:

picture: load http://rebol.com/view/bay.jpg

The above line creates a variable (label) that can be used in your program anywhere you want to read and use the file located at http://rebol.com/view/bay.jpg . The built-in REBOL word "load" does the actual work of going out to the web site address and collecting the image data.

Now you can use the word "picture" to refer to the above image. Display it in a GUI using the following code:

view layout [image picture]

The words "view", "layout", and "image" are built into REBOL, and the word "picture" is now just as valid, because it's been defined to the interpreter.

Because the word "picture" is _variable_, you can also redefine and _change_ the data referred to by it:

picture: load http://rebol.com/view/demos/palms.jpg

Now, when you use the word "picture" in your program, it refers to a different file at a different Internet location. Writing the same GUI code now displays that different photo:

view layout [image picture]

You can also make the word "picture" refer to a file on your hard drive, or anywhere else you'd like:

picture: load %/c/bay.jpg

Here are some more examples of creating and using variable words. Type them into the REBOL interpreter to see how they work, and to understand how assigned words can take the place of _any_ data. NOTE: anything after a semicolon in REBOL code is ignored by the interpreter. It's used to include human readable comments in the code. You don't need to type any of the comments into the interpreter:

acolor: "blue"

alert acolor ; alerts you with a dialog box displaying the text "blue"

print acolor ; prints the word "blue" in the REBOL interpreter

anumber: 12

print anumber ; prints the number 12 in the interpreter

computation: (10 + 12) / 2

print computation ; prints the answer

filename: request-file

print filename ; prints the name of the user chosen file

chosen-color: request-color

print chosen-color

answer: request "Are you having fun yet?"

print answer

pick-a-date: request-date

print pick-a-date

userpass: request-pass

print userpass

webpage: http://rebol.com

browse webpage

file: %./test.txt

editor file 
; launches Rebol's built in text editor and opens the filename 
; assigned above

email-address: user@webpage.com

message:  "Hi Luke.  How are you doing?"

send email-address message

The key is to be aware of the colon character (":"). It's the thing that tells REBOL to create a new variable word and assign it to some value that'll be used later.

12. Blocks and Series

In REBOL, you can use words to represent multiple pieces of data grouped together, collections of other word variables, and other programming elements. Just surround the data in brackets. Type in the code below to see how it works:

somecolors: ["red" "yellow" "blue" "black"]
; "somecolors" is now a defined word used to represent the entire 
; data block enclosed in brackets.
print somecolors

In REBOL, collections like this are called "blocks", and they're very important. In fact, they're the primary organizational unit in REBOL and the main structure in which data is stored in the language. That's very important - remember, programming is all about inputting, storing, retrieving and otherwise manipulating data. In REBOL, ANY type of data can be assigned a word, and blocks can contain any combination of words and raw data. Blocks provide a simple way of managing every conceivable type of data, and that's at the heart of all programming. Learning to work with blocks is therefore a fundamental part of learning Rebol.

You've already used blocks - brackets were snuck in earlier to display GUI elements and to perform actions on them. Any group of words surrounded by brackets forms a block, and you can assign a word to refer to that block. Words referring to complex blocks of data can be likewise grouped together into other blocks simply by surrounding the words with brackets. This makes dealing with very complex and useful data structures very easy in Rebol.

Type in the code below to see how an entire GUI layout can be built and represented by using one word label:

gui-layout: [button field text-list]

"gui-layout" now refers to that entire block of code. You can display it using the standard "view layout" syntax:

view layout gui-layout

Blocks of data can also be spread out over several lines, and can be separated by extra white space. Just surround the elements with brackets:

gui-layout2: [
    button red "Click Me"
    field "Enter some text here."
    text "REBOL is really pretty easy to program." purple
    text-list 400x300 "line 1" "line 2" "another line"
    check yellow
]

Now display it in a GUI:

view layout gui-layout2

Layout blocks much bigger than that can be stored in a single word!

NOTE: It's standard practice to indent compound blocks with consecutive tab stops. Starting and ending brackets are typically placed at the same indentation level. This is conventional in most programming languages, because it makes complex code easier to read. For example, the compound block below:

[blue red green [1 2 4 [jan feb march [monday tuesday wednesday]]]]

can be written a bit more clearly as:

[blue red green 
    [1 2 4 
        [jan feb march
            [monday tuesday wednesday]
        ]
    ]
]

Indentation is not required, but it's very helpful when dealing with more intricate programming structures.

Here's a simple data table example, containing schedule information within a block:

schedule: [
    ["John Smith" "Monday" "3:00 pm"]
    ["Dave Jones" "Tuesday" "11:00 am"]
    ["Janet Duffy" "Wednesday" "4:45 pm"]
]

You can display the above block in a GUI, using the built-in REBOL word "list". Notice the "data schedule" code in the last line below - it inserts the entire schedule block defined above as data to be displayed in the list widget (the line "across text 150 text 150 text 100" is just syntax expected by the list widget to define its graphic layout):

view layout [
    vh2 "This Week's Appointments:"
    list 600x400 [
        across text 150 text 150 text 100
    ] data schedule
]

The beginnings of a graphical database application, in several lines of code... Not tough at all!

Using blocks to store collections of data is similar to using variable words to store single values. Once a word is assigned to represent a block of data, that word can be used in place of the actual data, to represent it in code. Blocks are simply able to respresent larger collections/varieties of data. You don't have to worry about how the computer stores or works with the data in memory. You can simply label the data and use it in it's natural human-understandable form, using a single consistent bracket syntax to deliniate each group of values. That's very powerful, and it's much easier than in other programming languages. In most other languages, formatting and using data in code requires quiet a bit more preparation, management, and a variety of code structures to handle different types of data.

Many of Rebol's built in words help you manipulate data stored in blocks. Type in the following code to see how the "sort" word works:

somecolors: ["red" "yellow" "blue" "black"] 

sortedcolors: sort somecolors

"Sort" is a built in function word that alphabetically (/ordinally) sorts the elements of a given block. The line above creates the newly defined word "sortedcolors", and assigns it to the sorted block of words contained in "somecolors".

print sortedcolors 
; This code displays the sorted block of text. 

print first sortedcolors 
; "first" is another built-in word.  
; It selects the first item in a given block.

find somecolors "red" 
; "find" is a built in word that searches for data within a block.

You can easily save blocks of data to your hard drive, read them from a web server, and perform other file operations with them.

write %/c/colors.txt somecolors 
; writes the entire block of text represented by "somecolors" 
; to a text file called colors.txt on the C: drive.

Here's an interesting twist, demonstrating how REBOL can easily mix datatypes within a block:

an-image: load http://rebol.com/view/bay.jpg
; downloads an image from the Internet and assigns the 
; word "an-image" to it.

append sortedcolors an-image 
; "append" adds the downloaded image to the end of the data block 
; currently containing the simple text words defined above.  
; Now the block contains both text and binary image data, 
; all assigned to a single word - not a problem in Rebol!

Now you can select items from that new block:

print first sortedcolors 
; prints the first item in the data block - the text "black".

view layout [image fifth sortedcolors] 
; displays the fifth item in the data block - 
; the image downloaded above - in a simple GUI.

That should get you thinking a bit. You can store images, sounds, text, complex data structures, and anything else you want in a block, all with equal ease. That's a complex ability which requires some hard core learning in most programming languages.

Here's some more notation to be familiar with when working with sequential data in blocks. That type of data is called a "series" in REBOL. All blocks of data are actually series in REBOL, and can be treated as sequential lists by default:

view layout [image sortedcolors/5] 
; "sortedcolors/5" is another way to refer to the fifth item 
; in a data block.

The following example puts the above notation to work in a useful way:

length-of-block: length? sortedcolors 
; the built in word "length?" returns the number of items 
; in the block (5 in this case).

view layout compose [image sortedcolors/(length-of-block)]

The word "compose" allows variables in parentheses to be evaluated and inserted as if they'd been typed explicitly into the code of the program. In the example above, the code reads as if sortedcolors/5 had been typed in manually. Another way to use variable values explicity is with the format below:

view layout [image sortedcolors/:length-of-block]

The colon symbol in front of a variable does the exact same thing as the compose function and parentheses above. You'll see both formats in REBOL code, so it's good to know both.

The following examples demonstrate additional words used to traverse sequential series of data within blocks:

insert sortedcolors "mauve" 
; adds the word "mauve" to the sortedcolors block of data.

remove sortedcolors
; removes the first item from the block.

head sortedcolors 
; sets a position marker at the beginning of the data block

next sortedcolors 
; sets a position marker at the next item in the data block

last sortedcolors 
; sets a position marker at the last item in the data block

back sortedcolors 
; sets a position marker at the previous item in the data block

tail sortedcolors 
; sets a position marker after the last item in the data block

The fact that you can mix together all types of data within a block, refer to parts of blocks by name, and access/alter data within them using built-in functions, is very powerful and useful. Blocks and variable words assigned to blocks help you store, manipulate, and refer to all the data you'll deal with in your programs. Doing so is similar to assigning nouns to groups of people, places, and things in spoken language. Within those groups, smaller groups and individual items can be named, ordered and otherwise organized - in the same way that complex database applications allow you to store and work with all types of information. The possibilities of dealing with data in that way are endless.

13. Function Words

It's easy to define your own function words (actions), once you know some of the built-in vocabulary of REBOL. Creating new functions is comparable to creating your own verb words in a spoken language. Just be careful not to unintentionally use words that are already defined in the REBOL language, or in your current program. That would change the meaning of the existing word. For example, you could accidentally change the meaning of the word "write" to refer to a picture on your hard drive by typing the following:

write: read %/c/bay.jpg   
; *** DON'T TYPE THAT IN - it'll change the meaning of the word 
; "write" in the REBOL interpreter (only for the current session).  
; It's an example of what not to do. ***

You can protect all of the built-in REBOL words by typing in "protect-system". That'll alert you with an error and disallow any attempt to redefine native REBOL words. You still must be careful not to accidentally redefine words that you've created. Word definitions only last for the current session, so if you make a mess, all you need to do is restart the REBOL interpreter.

Here's a really important concept: (drum role) ... In REBOL, you can use single words to represent entire blocks of actions (i.e., collections of function words grouped together). In fact, blocks like that form the primary code sections that make up programs in the REBOL language. Here's an example of several function words grouped together into a block (i.e., enclosed in brackets), and assigned a new function word:

some-actions: [
    alert "Here is one action." 
    print "Here's a second action."
    write %/c/anotheraction.txt "Here's a third action."
]

The above code has created a kind of super-verb that refers to several actions. You can perform the actions contained in any block using the "do" function word. To perform all the actions in the above code block, just type:

do some-actions

You can also include the word "does" right inside a word definition - that'll make the actions (function words) contained in the block perform automatically every time the new word is used in Rebol:

more-actions: does [
    alert "4" 
    alert "5"
    alert "6"
]

In fact, by including the "does" command right in the word definition, you've just created a new function (or "subroutine") that can be used like any other built-in action word in Rebol! You can now talk to the interpreter using that word, and it understands what you mean for it to do. After you've entered the code above, try typing the word "more-actions" into the REBOL interpreter:

more-actions

It's taken on a life of its own! As with variable words, the key is to be aware of the colon character - that's the symbol that actually creates the new function word.

Here's an example of a useful little action word to clear the command line screen in the REBOL interpreter.

cls: does [prin "^(1B)[J"]

The native way to clear the REBOL interpreter's command screen is by typing "prin "^(1B)[J". That's kind of awkward to type, and even tougher to remember. Instead, we can assign the word "cls" to perform the action - just like in old Basic languages. Now just type:

cls

and the screen clears - that's much easier.

Here's a little program that creates the new action word "send-email". It provides a simple text requestor interface for users to send email:

send-email: does [
    email-address: to-email request-text/title/default 
        "Enter an email address:" "user@webpage.com"
        ; the above line creates the new variable word "email-address"
        ; "email-address" is assigned the value of the 
        ; text input using the built in word "request-text"
        ; the "title" and "default" refinements customize the 
        ; info displayed in the text requestor.
    message: request-text
        ; the above line creates the new variable word "message" 
        ; and assigns it to some requested text
    send email-address message
        ; the above line sends the user-provided message to the 
        ; email address given earlier
    alert "Your message has been sent."
]

send-email ; do the routine above
send-email ; do it again to send another message to someone else

The above process is VERY important. It's the basis of how you perform more complex and useful actions in REBOL. Blocks of actions and blocks of data form the basis of how you program in REBOL. And performing actions upon data is what you'll do to complete all your programming tasks. In REBOL you just group bits of data together into blocks, and assign a name to refer to it. You also group functions together into blocks to perform actions upon the data, and assign a name to refer to those actions. You can even combine complete groups of data and functions into blocks that can be assigned unique word identifiers that perform complete programmatic tasks for you: store and manipulate data all with a single word that you define! That allows you to create your own unique language in any program you write, using words that you define. You could create, for example, your own language that reads something like "email tom at noon with the main news page from yahoo.com". Those words aren't built into REBOL, but they can be assigned the appropriate action and data meanings to make that sentence a completely functional piece of code that the computer understands! (It would still need to be syntactically correct and exact). Building word meanings in that way is called building a "dialect" in REBOL, and it's one of the ways REBOL is different from other languages. You could conceivably write a dialect for programming robotic devices that reads "Vacuum the floor. Move in concentric circles around the perimeter". You could write a dialect for doctors that reads "print prescription for John Smith" (Ponder for a moment the possibilities for that type of natural language capability and flexibility...). Translating the existing REBOL language to any other language is as simple as assigning built-in function/data words to words in the desired language (for example, translating the print function to French, Italian, and Spanish is as simple as typing "imprime: stampa: impresion: :print"). Other languages focus on different ways of grouping and managing functions and variables. Typically, other languages use more cryptic ways of doing things. For example, blocks that contain both fully encapsulated functions and variables are called "objects". You'll see more about "object-oriented" programming as you study REBOL and other languages in depth...

14. Several Ways to Create Functions in REBOL - Passing Variables

There are several built-in words in the REBOL language that allow you to create more complex function words. To create simple functions, you can use the "does" command, as described above. But some functions are more complicated than that. They perform work upon _variable_ data. For example, the following simple function displays the square root of 4. That's all it can do:

sqr-four:  does [print square-root 4]

This type of function is similar to what you saw in the previous section. The word "sqr-four" is now assigned to the action "print square-root 4" (the word "square-root" is built into Rebol). After entering the above line in the REBOL interpreter, type:

sqr-four

It'll give you the expected result of 2.

Say now, however, that you now want to do something using numbers other than 4... What if you want to create a function that adds 4 to some other number, and then computes the square root of that sum? For that to work, the other number must be changeable, and therefore must be assigned a variable name. That variable name can then be "passed" to the function. The built in word "func" is used to create functions to which changeable variables can be passed. The syntax for the word "func" expects it to be followed by two blocks of code. The first block contains the name(s) of the variables to be passed. The second block contains the actions to be taken. Here's how it looks:

func [names of variables to be passed] [
    actions to be taken with those variables
]

In the following line, a function is created in which a variable word "anumber" is named and passed. That whole function is assigned the word "sqr-var":

sqr-var: func [anumber] [print square-root (4 + anumber)]

Now you can use the word "sqr-var", and the REBOL interpreter knows what to do with the assigned data. Try the following code:

sqr-var 12 ; prints "4", the square root of 12+4 (16)
sqr-var 96 ; prints "10", the square root of 96+4 (100)

Printing the square root of 4+somenumber may not sound so exciting to you, but it helped to illustrate one of the most important techniques employed in all modern programming languages. The process of passing variable parameters to functions is a fundamental part of all modern programming. It's perhaps the single most common element in contemporary languages, and understanding how it's done is absolutely essential. Below are some more interesting real-world examples.

The following line creates a simple function to display images:

display: func [filename] [view layout [image load filename]]

It accepts an image filename as the passed parameter (%somefile.jpg, %somefile.gif, %somefile.png, or %somefile.bmp), and then creates a GUI to display the image. That set of actions is assigned to the word "display". Simple, right? Now you can use the word "display" like this:

image1: to-file request-file/title trim {
    Select an image from your hard drive:} ""
; gets an image filename from the user

display image1
; displays the above image using the new function word

display http://rebol.com/view/bay.jpg
; displays the image at the above url

display %/c/bay.jpg
; displays an image that was saved to the
; hard drive earlier in this tutorial

Once the word "display" is defined in your programs, you can use it as if it's a built in REBOL action word.

Here's an example that asks the user for 2 website urls, and then opens those sites in separate brower windows:

openwebsite: func [nameofwebsite] [browse nameofwebsite] 
; The line above creates a new function that passes a url 
; to the built-in REBOL word "browse", to open the passed 
; web site name in the user's default browser.  It also 
; assigns the new function word "openwebsite" to that set
; of actions.

website1: request-text/title "Enter a web site URL:"
; The line above assigns a new variable word "website1" to text 
; returned by the built-in REBOL "request-text" function.

website2: request-text/title "Enter another web site URL:"
; Gets some more text and assigns the new variable word "website2"
; to it.

openwebsite website1
; The line above uses the new "openwebsite" function word defined
; above, and passes it the "website1" variable word

openwebsite website2

; Uses the openwebsite function again, this time passing the
; website2 variable

In that example, the word "openwebsite" is assigned to a new function definition. "Website1" and "website2" are labels for variables passed to that function. Below is a variation which assigns a single word to that entire process:

display-website: does [ 
    openwebsite: func [nameofwebsite] [browse nameofwebsite] 
    website: request-text/title "Enter a web site URL:"
    openwebsite website
]

Now you can use the single word "display-website" in your programs to do that entire block of code.

display-website

Getting used to the above syntax and way of thinking is absolutely essential. Remember, dealing with all types of data is the main thing you'll do as a programmer. Passing variable data to functions that you create is the main way you'll perform actions upon data in all your programming. That's the current state of programming, and it's what you'll do in virtually every programming situation. REBOL makes it easy to deal with data, using its built in support for most common data types. Understanding how to input, manipulate, and output that data is your main objective. Using functions and variables as described above is a fundamental part of that process.

15. Conditional Operations

Programs often need to make decisions based on user input, program states, data content, etc. "If the user selects this option from a list, respond by executing this function". That's a common situation. "If a certain amount of time has passed, save data to the hard drive". Giving the computer a variety of actions to perform, based on a variety of expected conditions, is a fundamental programming technique used in all languages.

Mathematical operators such as = < > <> (equal, less-than, greater-than, not-equal) are typically used to perform conditional operations. Type in the following code to see how a conditional operation works:

if now/time > 12:00 [alert "It's after noon."]  
; now/time is a variation, or "refinement" of the built-in 
; function "now", that returns only the current time.

Here's a more complex example:

daily-calories: to-integer request-text/title {How many 
    calories have you eaten today?}
; gets some info from the user and assigns the variable word
; "daily-calories".  The built-in "to-integer" function
; helps make sure REBOL interprets that info as a number.
; The "{}" characters surrounding the title text work the 
; same way as quotes, but allow the string of text to 
; span several lines.  Very important.

if daily-calories > 2500 [alert "You need to stop eating now."]

The built-in REBOL word "either" chooses between two blocks of functions to perform, based on a conditional evaluation. Its syntax is:

either {condition} [
    block to perform if the condition is true
][
    block to perform if the condition is false
]

Here's an example:

either now/time > 8:00am [alert "It's time to get up!"] [
    alert "You can keep on sleeping."]

Here's a variation of the above example that allows you to set the wake up time:

wake-up: to-time request-text/title "What time do you want to wake up?"

either now/time > wake-up [alert "It's time to get up!"] [
    alert {You can keep on sleeping."}]

The built-in REBOL word "switch" chooses between numerous functions to perform, based on conditional evaluations. Its syntax is:

switch/default (main value) [
    (value 1) [block to execute if value 1 = main value]
    (value 2) [block to execute if value 2 = main value]
    (value 3) [block to execute if value 3 = main value]
    ; etc...
] [default block of code to execute if none of the values match]

You can compare as many values as you want against the main value, and run a block of code for each matching value. Here's an example:

favorite-day:  request-text/title "What's your favorite day of the week?"

switch/default favorite-day [
    "Monday" [alert "Monday is the worst!  Just the start of the week..."]
    "Tuesday" [alert "Tuesdays and Thursdays are both ok, I guess..."]
    "Wednesday" [alert "The hump day - the week is halfway over!"]
    "Thursday" [alert "Tuesdays and Thursdays are both ok, I guess..."]
    "Friday" [alert "Yay!  TGIF!"]
    "Saturday" [alert "Of course, the weekend!"]
    "Sunday" [alert "Of course, the weekend!"]
] [alert "You didn't type in the name of a day!"]

"Switch" gets used a lot because programs often require numerous multiple evaluation results to choose from.

REBOL includes a rich set of words and functional structures that help you evaluate conditions in all types of situations, and with all types of data. Understanding how to use them is a big part of learning the language.

16. Looping

Programs are often required to check conditions and to execute functions repeatedly. In fact, in most large applications the computer often loops through many instances of repetitive work. For example, in a reminder program, the application may need to continually check the time and date to see if the user should be reminded of a given event at the moment. In other types of programs, the computer may need to repetitively scan through collections of data, or repeatedly request/respond to user input. To handle such situations, "loop" structures provide programmatic ways to methodically repeat actions.

The built in word "forever" creates a simple repeating loop. Its syntax is:

forever [block of actions to repeat]

The following code builds a simple timer that alerts the user when one minute has passed. It uses a forever loop to continually check the time.

alarm-time: now/time + 60  
; assign a variable to the time 60 seconds from now
forever [if now/time = alarm-time [alert "60 seconds has passed" break]]

Notice the word "break" in the example above. It exits the forever loop so that the program doesn't run on forever once the alert has been displayed.

Here's a more interactive version that uses some info provided by the user. The heart of the program is still the loop at the end:

event-name: request-text/title "What do you want to be reminded of?"
; requests the name of an event from the user
seconds: to-integer request-text/title trim {
    How many seconds do you want to wait?}
; requests a number of seconds to wait
alert join "it's now " [
    now/time ", and you'll be alerted in " seconds " seconds."
]
; display a message
alarm-time: now/time + seconds
; set the alarm time
forever [
    if now/time = alarm-time [
        alert join "it's now " [
            alarm-time ", and " seconds 
            " seconds have passed.  It's time for: " event-name
            ] 
    break
    ]
] 
; the forever loop continually compares the set alarm time to
; the current time, then displays an alert when they match.

IMPORTANT: Notice the "join" word used in several of the lines above. It's syntax format is:

join {data} [block of data items]

Using that format, it joins together variables, text, blocks and other bits of data, so that they can be printed together, displayed, and otherwise manipulated to form a single piece of data. It's very useful! A variation on join is "rejoin". It takes a single block of data, and concatenates all the individual items into one piece of data. You should be familiar with this syntax too:

rejoin [item1 item2 item3 ...]

Now, back to loops. Here's a simple forever loop that displays/updates the current time in a GUI. Notice the block indentation:

view layout [
    timer: field
    button "Start" [
        forever [
            set-face timer now/time 
            wait 1
        ]
    ]
]

The above GUI contains two widgets: a text field which is assigned the variable label "timer", and a button with the word "Start" on it. The action block for the button contains a forever loop that loops 2 actions repeatedly (until the user closes the GUI): the built in word "set-face" sets the text in the "timer" field to the current time, the program waits 1 second, and then loops.

Often, the data dealt with in each repetition of a loop must change. Like most languages, REBOL includes a variety of functions and programmatic structures that allow you to loop through blocks of data and perform operations using consecutively changed values. A common looping structure in many languages is the "for" structure. It allows you to specify a starting value, an ending value, an incremental value, and a variable name to hold the current value, so that you can loop through consecutively changing values in a controlled way. Here's the basic REBOL syntax for a "for" loop:

for {variable word to hold current value} {starting value} {ending value} {incremental value} [block of code to perform, which can make use of the current variable value]

Here are some simple examples. Be sure to type them in to see how they work:

for counter 1 10 1 [print counter] 
; starts on 1 and counts to 10 by increments of 1

for counter 10 1 -1 [print counter] 
; starts on 10 and counts backwards to 1 by increments of -1

for counter 10 100 10 [print counter] 
; starts on 10 and counts to 100 by increments of 10

for counter 1 5 .5 [print counter] 
; starts on 1 and counts to 5 by increments of .5

for timer 8:00 9:00 0:05 [print timer] 
; starts at 8:00am and counts to 9:00am by increments of 5 minutes

for dimes $0.00 $1.00 $0.10 [print dimes] 
; starts at 0 cents and counts to 1 dollar by increments of a dime

for date 1-dec-2005 25-jan-2006 8 [print date] 
; starts at December 12, 2005 and counts to January 25, 2006 
; and by increments of 8 days

for alphabet #"a" #"z" 1 [prin alphabet] 
; starts at the character a and counts to z by increments of 1 letter

Notice that REBOL can easily loop through various types of data, forewords, backwards, and in partial increments, with a native understanding of those data types. It can automatically increment dates, money, time, etc. In other languages, that can be tough to do.

Also, notice the use of the "prin" word in the last example. It works like "print", except it doesn't automatically insert a carriage return (i.e., it prints each successively output character next to the previous one, instead of on separate lines).

Here's a "for" loop example that displays the first 5 file names in the current folder on your hard drive:

files: read %.  
; gets the current directory list, 
; and assigns that block of filenames to the variable "files"

for count 1 5 1 compose [print files/(count)] 
; starts printing with the 1st item in the block, 
; and counts up to the 5th item.

In the example above, "files/1" is the syntax representing the first item in the file list, "files/2" represents the second, and so on. Notice the "compose" word used in the for loop. In the example above, the first time though the loop, the code reads as if [print files/1] had been typed in manually, etc.

"Foreach" is another useful looping structure built into REBOL. It lets you easily loop through a block of data. Its syntax is formatted as follows:

foreach {variable name referring to each consecutive item in the given block} [a given block] [block of functions to be executed upon each item in the given block, using the variable name to refer to each item successively]

The example below prints the name of every file in the current directory on your hard drive:

folder: read %. 
; gets the current directory list, 
; and assigns that block of filenames to the variable "folder"

foreach file folder [print file] 
; loops through each file name contained in the "folder" block, 
; and prints each one consecutively.

The following line reads and prints each successive message in a user's email box:

foreach mail read pop://user:pass@website.com [print mail]

"While" is other useful looping structure found in most programming languages. It repeatedly performs a conditional evaluation, and then performs a block of code while the condition is true. While loops are formatted as follows:

while [condition] [
    block of functions to be executed while the condition is true
]

Here's a simple example:

x: 1  ; create an initial counter value 
while [x <= 5] [
    alert to-string x 
    x: x + 1
]

In English, that code reads "x initially equals 1. While x is less than or equal to 5, display the value of x, and add 1 to the value of x". (i.e., the program displays a count from 1 to 5)

NOTE: In REBOL, the code "x: x + 1" adds 1 to the current value of x. It's one of the most commonly used expressions in all programming, found in all sorts of looping situations. Notice also the word "to-string". It converts the number value in "x" to a text ("string") value. This is required because the "alert" word in REBOL only displays string types of data.

Here are some additional "while" loop examples:

while [not request "End the program now?"] [
    alert "Select YES to end the program."
]

In the above example "not" reverses the value of data received from the user (i.e., yes becomes no and visa versa)

alert "Please select today's date" while [request-date <> now/date] [
    alert join "Please select TODAY's date.  It's " [now/date]]

while [request-pass <> ["secret" "password"]] [
    alert "The username is 'secret' and the password is 'password'"]

The example below uses several loops to alert the user to feed the cat, every 6 hours between 8am and 8pm. It uses a for loop to increment the times to be alerted, and a forever loop to do the same thing every day, continuously. Notice the indentation:

forever [
    for timer 8:00 14:40:00 6:00 [
        if now/time = timer [
            alert join "It's now " now/time ".  Time to feed the cat."
        ]
        ; if not, continue looping until the next meal time arrives.
    ]
]

17. Working With Longer Examples

Most programs are longer than the examples shown so far in this tutorial. Below is an example of a more complex program. It allows the user to type in the Internet address of a web cam, and display streaming video from it. It displays the video images in a GUI that has several buttons used to control the size, location, and action on screen. In order to avoid typing in this program every time you run it, it can be saved as a text file. Whenever you save a REBOL program to a text file, the code must begin with the following bit of text:

REBOL []

That text tells the REBOL interpreter that the file contains a valid REBOL program. It must be included at the beginning of any saved REBOL program. You should include additional documentation about the program such as title and version info in between the brackets, but it's not required.

Type in or copy/paste the complete code source below into a text editor such as Windows Notepad. You can also use the built in REBOL text editor by typing "editor" in the REBOL interpreter. Save the text as a file called "webcam.r" on your C:\ drive.

(Note: this example includes a number of REBOL words and techniques that have not yet been discussed in the tutorial. The purpose of the example is simply to provide a longer text example that can be cut, pasted, and saved into a file. Don't worry if you can't understand the code at this point.)

REBOL [Title: "Webcam Viewer"]

; try http://www.webcam-index.com/USA/ for more webcam links.

temp-url: "http://209.165.153.2/axis-cgi/jpg/image.cgi"
while [true]  [
    webcam-url: to-url request-text/title/default trim {
        Enter the web cam URL:} temp-url
    either attempt [webcam: load webcam-url] 
        [break]
        [either request [trim {
            That webcam is not currently available.} trim {
            Try Again} "Quit"]
            [temp-url: to-string webcam-url]
            [quit]
    ] 
] 
resize-screen: func [size] [
    webcam/size: to-pair size
    window/size: (to-pair size) + 40x72
    show window
]
window: layout [
    across 
    btn "Stop" [webcam/rate: none show webcam]  
    btn "Start" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    rotary "320x240" "640x480" "160x120" [
        resize-screen to-pair value 
    ]
    btn" Exit" [quit] return
    webcam: image load webcam-url  320x240 
    with [
        rate: 0
        feel/engage: func [face action event][
            switch action [
            time [face/image: load webcam-url show face]
            ] 
        ] 
    ] 
]
view center-face window

Once you've saved the webcam.r program to C:\, you can run it in any one of a variety of ways:

  1. If you've already installed the REBOL interpreter in Windows, just find the C:\webcam.r file icon in your file explorer and double click it (i.e., click My Computer -> C: -> webcam.r). The REBOL interpreter automatically executes the script. By default, during Rebol's initial installation, all files with an ".r" extension are associated with the interpreter. They can be clicked and run as if they're executable programs, just like ".exe" files. This is the most common way to run REBOL scripts, and it works the same way on all major graphic operating systems.
  2. Type "do %/c/webcam.r" into the REBOL interpreter.
  3. Use the built-in editor in REBOL by typing "editor %/c/webcam.r" at the prompt. Pressing F5 in the editor will automatically run the script.
  4. Scripts can be run at the command line. In Windows, copy rebol.exe and webcam.r to the same folder (C:\), then click Start -> Run, and type "C:\rebol.exe C:\webcam.r" Those commands will start the REBOL interpreter and do the webcam.r code.
  5. At the Windows command prompt (in a Windows DOS box), type "C:\rebol.exe C:\webcam.r"
  6. Create a text file called webcam.bat, containing the text "C:\rebol.exe C:\webcam.r" . Click on the webcam.bat file in Windows, and it'll run those commands.
  7. Use a program such as XpackerX to package and distribute the program. XpackerX allows you to wrap the REBOL interpreter and webcam.r program into a single executable file that has a clickable icon, and automatically runs both files. That allows you to create a single file executable Windows program that can be distributed and run like any other application. Just click it and run...
  8. Buy the commercial "SDK" version of REBOL, which provides the best method for packaging REBOL applications.

IMPORTANT: To turn off the default security requestor that continually asks permission to read/write the hard drive, type "secure none" in the REBOL interpreter, and then run the program with "do {filename}". Running "C:\rebol.exe -s {filename}" does the same thing . The "-s" launches the REBOL interpreter without any security features turned on, making it behave like a typical Windows program.

17.1 "Compiling" REBOL Programs - Distributing Packaged .EXE Files:

Being able to save, run, and distribute your programs is important. You should become very familiar with the options above. Using the XpackerX packager is especially useful (not just for REBOL, but for other interpreted languages too). It saves your users from having to download, install, or run the REBOL interpreter. By packaging the Rebol.exe interpreter, your REBOL script(s), and any supporting data file(s) into a single executable with an icon of your choice, it works like a REBOL compiler that produces regular Windows programs that look and act just like those created by other compiled languages. To do that, you'll need to create a text file in the following format (save it as "template.xml"):

<?xml version="1.0"?>
<xpackerdefinition>
    <general>
        <!--shown in taskbar -->
        <appname>your_program_name</appname>
        <exepath>your_program_name.exe</exepath>
        <showextractioninfo>false</showextractioninfo>
        <!-- <iconpath>c:\icon.ico</iconpath> -->
    </general>
    <files>
        <file>
            <source>your_rebol_script.r</source>
            <destination>your_rebol_script.r</destination>
        </file>
        <file>
            <source>C:\Program Files\rebol\view\Rebol.exe</source>
            <destination>rebol.exe</destination>
        </file>
        <!--put any other data files here -->
    </files>
    <!-- $FINDEXE, $TMPRUN, $WINDIR, $PROGRAMDIR, $WINSYSDIR -->
    <onrun>$TMPRUN\rebol.exe -si $TMPRUN\your_rebol_script.r</onrun>
</xpackerdefinition>

Just download the free XpackerX program and alter the above template so that it contains the filenames you've given to your script(s) and file(s), and the correct path to your REBOL interpreter. Run XpackerX, and it'll spit out a beautifully packaged .exe file that requires no installation. Your users do not need to have REBOL installed to run this type of executable. To them it appears and runs just like any other native compiled Windows program.

There are a variety of other options you can use to create distributable REBOL .exe programs. All such options involve creating self-extracting executable zip files ("SFX" files) that package the Rebol.exe interpreter together with your scripts. When run, the SFX files automatically open Rebol.exe, and execute your script on the command line. StubbieMan SFX is such a program, made specifically for creating packed executables, using a simple wizard interface. You can also use any other zip archiving application capable of creating SFX files. For example, PowerArchiver (version 6.11) is a popular free zip manager. To package REBOL executables with PowerArchiver, just follow the instructions to create a zip file that includes Rebol.exe and your script/data files, select "Actions->Make .EXE file" from the main menu, select "Create AutoRun SFX" and "Overwrite Files", then type "Rebol.exe| -s yourscript.r" into the field labeled "Command line after extracting" . It'll pop out a compact file executable with even less work than XpackerX or StubbieMan. Full instructions for creating self-extracting executables are included in the PowerArchiver help file. PowerArchiver and StubbieMan do not provide an option to change the resulting program icon. XpackerX is a bit tougher to use, but it does allow you to choose a specific icon for your program.

The most complex distribution option is to create a standard installation package. To do this, try NSIS, the NSIS SFX Tool (my favorite), Inno Setup or Pack-X2. These programs all create single file .exe install packages that provide users with a familiar installation routine, uninstall option, and a group icon in the "Start" menu, so that your REBOL program can be accessed and run like any other installed program. (Note that these programs are all for MS Windows. You'll need to find a different packaging system for other operating systems).

To create a self-extracting REBOL executable for Linux, first create a .tgz file containing all the files you want to distribute (the REBOL interpreter, your script(s), any external binary files, etc.). For the purposes of this example, name that bundle "rebol_files.tgz". Next, create a text file containing the following code. For the purpose of this example, save this script file as "sh_commands":

#!/bin/sh
echo ""
echo "Running... please wait"
echo ""
SKIP=`awk '/^__REBOL_ARCHIVE__/ { print NR + 1; exit 0; }' $0`
tail +$SKIP $0 | tar xz 
exit 0
__REBOL_ARCHIVE__

Finally, use the following command to combine the above script file with the bundled .tgz file:

cat sh_commands rebol_files.tgz > rebol_program.sh

The above line will create a single executable file named "rebol_program.sh" that can be distributed and run by end users. The user will have to set the file permissions for rebol_program.sh to executable before running it ("chmod +x rebol_program.sh"), or execute it using the syntax "sh rebol_program.sh". For more information about using this technique to create self-extracting Linux executables, see the article at http://linux.org.mt/article/selfextract.

18. Embedding Binary Data

You'll often need to use images and other binary data in your REBOL programs. For example, simple games often use graphic and sound files as part of their interface. As you've seen, there are many ways for REBOL to load in such binary data. You can read it from a hard disk, you can download it from the Internet, you can read it from just about any other local or networked storage medium, etc. When distributing your REBOL programs, however, those methods aren't always desirable. You don't want to require your user to download a number of images and sounds every time they play a simple game. They may not always have an Internet connection available. A possible alternative is to make a zip file or similar package that includes your program and all its supporting files. Creating and distributing such packages, however, can be overly complicated for simple scripts. Also, various operating systems use different compression types (zip, tar, etc.) and XpackerX/PowerArchiver/etc. only work in Windows. To eliminate those problems, REBOL provides a method to encode and include external files within the text of your programs. To see how it works, use the code below:

REBOL [Title: "Binary Embedder"]

system/options/binary-base: 64
file: to-file request-file/only
data: read/binary file
editor data

Type that program into your text editor, save it as a text file in "C:\embed.r", and then run it using any of the methods described in the previous section (try typing "do %\c\embed.r" into the REBOL interpreter). When run, the program will let you select a file, read it, and then display a binary representation of its data in the built-in editor. You can copy and paste the text of that printout directly into your program, assign it a variable name, and use it as if it had been read straight from a hard drive or another storage medium.

Here's an example. Download the picture below - you can use your web browser to go to the url, and then save the image to your hard drive. Or, you can download it using REBOL, as demonstrated earlier in the tutorial:

http://musiclessonz.com/test.png

Once you've downloaded the file, use the program above to select and convert it to binary data. You should get the following printout:

64#{
iVBORw0KGgoAAAANSUhEUgAAAFUAAABkCAIAAAB4sesFAAAAE3RFWHRTb2Z0d2Fy
ZQBSRUJPTC9WaWV3j9kWeAAAAU1JREFUeJztlzEOgzAQBHkaT7s2ryZUUZoYRz4t
e9xsSzTjEXIktqP3trsPcPPo7z36e4/+3qO/9y76t/qjn3766V/oj4jBb86nUyZP
lM7kidKZPFE6kydq/Pjxq/nSElGv3qv50vj/o59++hNQM6Z93+P3zqefAw12Fyqh
v/ToX+4Pt0ubiNKZPFE6Ux5q/O/436lkh6affvrpp38ZRT/99Ov6+f4tPPqX+8Ps
/meidCZPlM7kidKZPFE6kydKZ/JE6UyeKJ3JE6UzeaJ0Jk+UzuSJ0pk8UTMmvn8L
j/7l/nC7tIkonekLdXm9dafSmeinn376D/rpp5/+vv1GqBkT37+FR/9yf7hd2kSU
zuSJ0pk8UTqTJ0pn8kTpTJ4onckTpTN5onQmT5TO5InSmTxROpMnasbE92/h0b/Q
//jR33v09x79vUd/73XvfwNmVzlr+eOLmgAAAABJRU5ErkJggg==
}

Now copy and paste that data into the REBOL interpreter and assign it a variable label, like this:

picture: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAFUAAABkCAIAAAB4sesFAAAAE3RFWHRTb2Z0d2Fy
ZQBSRUJPTC9WaWV3j9kWeAAAAU1JREFUeJztlzEOgzAQBHkaT7s2ryZUUZoYRz4t
e9xsSzTjEXIktqP3trsPcPPo7z36e4/+3qO/9y76t/qjn3766V/oj4jBb86nUyZP
lM7kidKZPFE6kydq/Pjxq/nSElGv3qv50vj/o59++hNQM6Z93+P3zqefAw12Fyqh
v/ToX+4Pt0ubiNKZPFE6Ux5q/O/436lkh6affvrpp38ZRT/99Ov6+f4tPPqX+8Ps
/meidCZPlM7kidKZPFE6kydKZ/JE6UyeKJ3JE6UzeaJ0Jk+UzuSJ0pk8UTMmvn8L
j/7l/nC7tIkonekLdXm9dafSmeinn376D/rpp5/+vv1GqBkT37+FR/9yf7hd2kSU
zuSJ0pk8UTqTJ0pn8kTpTJ4onckTpTN5onQmT5TO5InSmTxROpMnasbE92/h0b/Q
//jR33v09x79vUd/73XvfwNmVzlr+eOLmgAAAABJRU5ErkJggg==
}

You'll get the response below, indicating that the image has be defined and created:

make image! [85x100 #{
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF...}

Now you can use the variable "picture" as you would any other data. Display it, save it, transfer it between networked computers, etc. Here it is in a GUI:

view layout [image picture]

The example below displays a photo of my dog, and then saves it to the hard drive as a .png image:

dog: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAFkAAACNBAMAAAAuisulAAAAMFBMVEUeEw6kh3OM
TRZDSE/RxbWFal1KLiCtkoakZSXW0cm9mnlCNzq5no5oSDTg29B0UkzBjRe5AAAA
CXBIWXMAAC4jAAAuIwF4pT92AAAQ80lEQVR4nG1Yf2gjZ3r+kiW6gjHJZHdOjuLU
W0GMKRgShG7/UIVqqu2eUtd3c9pvpe6uGJ32dnqca+zhIoa6glubczCBZVsQhGkX
9UR369zgEKeqXeic0G6xERHTxLGxWFCH+WehGIKIVx2viTJ8fd9v5M2m9JN/CPx8
zzzv8/74PpkIQTUYVDUtqMFP+L22uKitaaXFktaEZZXgyzpdD0hQEFRcz+CA10pr
HIurVLKa36KpAHhhAA8CeBHIm8BcwgXA5+APCGM0GBSCQU4ML5VvaDWfLav5HDez
40dAHhSQHaHwDNUw1pB5EXiR3hqIR/S+I3FupA4iWDPWjRYXUlovIfe3SuRcp+6q
Ag92QK5ebVmGhgFqKMQq8e8Sj5LadkMQnoFBh321ZTSbmtZcszBIHuuAW5L6pv1U
OPUFvhy3FbpnlLQScnPDTzc8IKLssc4Bxsmlgw67fdlo7WjWwMJBmPgFuj3W7zuc
G/wGx4PC3Kix/THsazZ9fNPHct2s0fC2JBW5ueuC91/r1nr6Ok++LwQf40cpyZ1+
o3+Anmg898VZuzX6+f4FID/VUnoWpTvm1Fmn4SdIQ+3Uvh/6OAJoXljIPjAQ0EfU
Yw22JQHcLys1/cX2vfuRaPM73AMlDvX6rNH5Arm54X9z45OL60YkgnnC7OAOyy8W
rCrZ6bP2pm94MFiMzb40tb6ejmSwvJrWoMx5/h8Qm8m2Z3v7AleuqbFI9BNrvfU6
CA8OytaXA/zALdO+Y7MnEk+QGolsmKMP32n9MvI2VkGz+XwP8eww16XyFz64Vntr
46+qllE0o6h7kTNDXvmOBeLYDnPlrHsTuIuRWiQSe/13Rsu4b9Z4mH4dwo41rtur
1z2XUgrCg2kAR8yEZRmGEakFfVMAvjbQskBYpwNSKLOF4NlYDNAbG9eNe8Z6j5uy
1iwtDroIFkRZ7zgMinz2SOXgmPlV6GurZfVqaAqKBkfWfO4HyN1waNGc/SJdQyGR
WnT0nXVjZz92QfNdATHgJP6GOjmGOmGsvvUJp45Ezpilq4bxT/COTxcYRc9sB91e
w5EZ65i1Wo3DzUDwfWM9B++CPhy0NK0BNwMowPtbphnx14ZlhCDIiJ97BA/qC7gp
ZbQGVWiasVP0N62fhK5BvCCczyJtbTCIsKqY3IMfpmnWfPRbLy++aqQBHeHDhXNj
dSG37bq5PmOJ3iz+HVeMXDDW/9NHa0E/obxaAG0yythmRO6btdqplO+1WuhJLePP
3NKpKwukwUSXNVw3fYoF0pes9c/xDS8soF9r+llaIHVJ7rMelKFZi32Ltjj6Wyl+
Gz0gW7LEWJ+5aTNyamHsTMvaQSU8nX7hLvpKzHlXhqpywO9TdCQw3RK5sAt+Yy/y
SJvNBTJXTx9BycZMns0aN5K0jCo+I1JrDsjXBp50zCO3waRrpjkLyA0SGDJjeahw
4IZnZXA0ojF8PC+Qrc4BTBR6La+Y9Y6iBAhRlI3R9RbnPk0nwuGwA0+2nIzsCa8T
Zc5htn1ntZ0PkOuW1ePwqCZwX4KLqAmibNejMpNyRHl0kpvIZulKm5DrrVAPpNQi
Mc0/6PwF3HN1lx24VwJvrtyZfHN1ZSmXy5PZg2Cac0cuqKr6HNo052SXuf0AXZm0
DyRh5D1QQsjNaxEeZkLjZ5fa9NGz7brnMHYlYNu2Yp9kaX4WwOTM6zVeZNHTowuT
Ckpmbc/NOcdKwW4rc7BlN5+/sZW/+X2QXat98uw8Rzxwzx04jiQrCkADbaXd2c0r
b3uBr38D9teikn9iYKAqcs/Z0Glf5PKAPgarbaVOhjobc6F/DkCy7AZVtec9cRwn
7tGOEphdOQ7Y7XZ7bjewFfja+CigbHU6diPDrxgaRqkuEJexHGWe8uIcvf0izOb2
7DXSrhvGq6TWUeYU+6B5yq1ybteTXY8MP6K5+YM7nq2wgKJUhY/JDa/TNh0m+Acj
KoJ54sA8dj1l+OAk+3O6cscLOCQ/N1qtEuKmZ2EAXxXUb7k3mStRdqwMe4p4kHVp
LnCg5OcMwSCk1rZPbHbET9ES8oMST6Ii27CH7Xw2m7NdOe/eHi9cNQRCAqbNnBO3
iuTWwG9ZEsV+3j4/pLgrVwIb9UcrdMVeCoXyhEQajic7SG1ZeCuCOqFUlNJ52w7k
Xa89fMyosJLNHhnGLqTfTMA5kwENpekm545RWRyTQVCeOCd5olBRGJHuvG8Yt0DK
0GbcZZhPranyKGXqjhXN/LyzG2B38oF5ikfhyteC+GMoxcANV04/5XcoPKVharqC
IPZNxesoNtTVAaIl+Q9CxitDCiFD0Wjubf92CTeABdKjY1RuNAAKy3OoJIgivfKL
6vu/ma1vkPwNNx4N8pslryrXFWGCs858p922nQMqHwhS9vgFQ/j+nA09RzbNTd4+
uGGBpNkBhSONzbdnoSps1v49OJW/ekEQXp1LIPpMrIG6+aULMu/yF2NbirJVNxXy
klSq5l+uVj+aW/EAPWSagsZ1Yy7rDegzWN5u3lQATDZDhkjmq9WReXqb95wZ9G9c
qKSD0AYMzo2AsgEtGThaN64E5sCZE5rNc3JB4zqC2isEkC6GyfL5PAGLX75utXaH
hkNidUWiu0huZhCMTbFA4p0j6qMJyQ8FyOaodT/fCRjgupRF9NbQU1DB75avELNB
JUT3kWaIBDJB60rgB0QQQmA85x7KwC1Hww2ge1Pk3MeIJRsBQRXzDiGhkGFU5TZn
gHnl3/+BG64b4KDHcEJtkTMSjQ/Bc65WBUPEkQjr3wS8cMEHjFfglILbPZX37a0N
CJ+c6e0rL8FzlkBKNeuhKeR7eBHFO/ePCcfKcr2zZZKhfv5GQlEOfkACV6uGIdA7
HP0yVwKefkVkF8+dXqfeCfRqdEOJBEwxT4alqiBUBZmjAxAxXgCFXSJx6r7Xr2+B
8UptP3AgbhDIzki1KmZ94XMuflYQgEWGKF0ZzqiOCSe+UtuapWMwkK8u3Qd2bjie
LYUJLOU8UUUq0V6stm/Os4ZMbhzPS1cIca6i4QJ9j6NhHClK+OQNQoIihfLvwYgE
NCU34pvCFTIEHWQAN7x9hoZFoC8pFTTITwyV5JVEgr5F5kXBGAGwX4UAz8MMViBk
0JGWKd5SYg3mbQ0n6NhG4EAQ0RI4V3xwWwF4ANAQZcaV5V4PTvp6vD7rlu6+VcuE
AAsbBF6zhHi37XYehMBE7kmy25Oj4GS93utcmOmmQfV94b4hQs+NbSD6Xwp79hOo
aJ7LNHzD6pkwnK/PdC+iH6ERcWlFWro3xS0cHx8P77W5EkGuJVxG0zSWkMXRbndK
DVn3YceH4pI4c/gWpmc8XCjMLecJEyFMvM4Uo4m06UjnKodfautWtWqIwtKSNJNc
APRwGLgLYIpMpSgk8whsT9N+jb7b7X6plqwd4cNQVUD03wL6xUKhoIyHzxNIvACF
Ar+CxUS67pa63cfC9dJU6EOQPiLM6H+GFoZhjY8DtyWqwR24VMNhJLu16Llut/tQ
spJCNWQsSdKM/g+oBIUUgJsW5SKI6blPNfFX0VjjOqDLglUxQqGqNCLO6P844AYx
MLTpAZXSImUylIucTvxRBeCHle7D6vvVEUF8V/8TRO+hhePnibPP5KD4UDzrRqlI
0+6FmUqle9g9bIHnwP0aV0ImUXc4T+Jxu+dCsRQxUFFMRD/rVgBcnjJC1RGRfqr/
KaILBZ/bi8MLGwhshNMtkv4MmLuHqQ9CSyNLAk3pf0wCdeXFcAGUg+59247HqewW
0/RteHmvge6KXvlvFZplSdL1B6TO2oFCGNjPk307ntjfx09IqgvJ76W/Qe7uzAdB
NbskZnT9lQbNHiun3HGPxSOuy+QLIgrq/bsO3F39cTIp0qVRXU9TaeWJUsAoz5Oe
E9+3HYqlSCVJjCbcFIR5qFf0aQG4U2UpKGa9AmAL44T0mQPUULVQsbBBZrnfgoVd
/bA8nR0RR1NlzaqKe1AkhfPgDe31bAduEWkqylBfCSZf7lYqlcOkNSVJwrs6oK0R
8GScNxEV7YTrwT1CALsBzORvdG5heQo67VO9DB8sxfEAthl4InmOg40DlQsvFkn0
6QwKP9SnBENI6eXtbeuNPWiy4QJoGYvXMD6QMQaFEmGA/uywWyl39UtqFdC6Nf0w
ayt5ZS8MnlxzDvADI5VgaNFcohGtb547hCB1valWReC2th/a7YKNhXKenN0/QLAY
1EQ3Gq9H3PpPL6PfUClBzn1p+mN7r4DtANyZiAfRjWVEYSftxGPM7bxwWUfZoEQV
dF1PNnN7T07R4lg8CjkMCsUii8ccx2vfRAvLlXIzpIqpsp7c8Zw9ZYB2pbRXlKWH
gnY2kbYTCda2r0M36F9OXwq2qqlyqnyxPdk+5abS5z13LLMjFYu93j4eQt5lqCvI
/KWSVQW/k9NtZbyw56N3opmEqz5UqVBMRPYbUC7HoAT06h+UrNanKPwaTvvwMqKb
NCP3mk2VBqWeF09AUnOXu4AB7net5l/rern8I6Uwh+Dl8+TSr9xfskwJjiza68eZ
K9DcX0CUun5417KsUbDw7lSuDdDl5fAtUiqmpQj+O0zIsHgcL57Hv9/FgtXvvmtZ
5yA9+vTYJFIj90MBToeSoDWFo17f9grHk23I/GESfLa2LSulJ/WyHA5PIDugNTUK
52xRUDNyxLEVZa89A5kE7qSV3C6BJ3ryYyCemMAowei0WBRLmaDI4jzFNlZg6hBq
dXu7qZeB/T+4f6hkVINTjcINXlJZHDqqEJ7AGLGszm1PN5PJZDn5owmf+xYoCWpH
VJAeSiKzMWWFX1TAk5ReuRvcti5D5svTF4EX4beAu6ip0O1FVWb2OPb2z2CyHZb1
u1S2Wj8rg/7kFHLDBsi8RoPQNEeqKHvIXZj8NZQgDIhL0vH2pd+VpVS5PBVe5XGe
J0UYZ+JZMbMDByyUfGF8kncxUDqxjJiZCkRTyallsA/YoWI1KmagYvGAxdFYmPwt
2FeZeVyOJ9i/Tv/5T7WMPrU8gUKWbxFVE0WQjTcxbxye94h+CgZWKjNlmrn0P2Ur
un12+yKCl1F3UwvCbHgqwPUU/AtPrkATH6KSSvIQivuzi9Jr537IDVwNI7cGrUNV
l8sOhyXxUxiCjzGZKTD93sxdIfnRKq8qjFKVZEiP6LoehBhWQgJwdzm2rM+k7pUr
enIEaSbCgM6oIESG+6bsYX4nJOGzFFY3wqG4p/VUahpV+35ndvBe9XTMZbwYlJs0
iY2DdTUD7JfK5fIPl1HGMvqtqYIruxmRHo/z0lHe3AbsDNDCK1X+u1Qq+SrECHAf
rUbhwClCjNB8sObvpRB4yHsz9fcpzPsyZwclF7Qig9GmHg8/CfvrXjKVgnRDhaTK
j7/cTmZ5blZXJ1Zvkb+Ez0aOTD/3z3Jcl5Plx2Wdy4E99+5jk61OLMPXLfISzTHK
pF1A+yMm/IdY02Xkr1SSZQrQMIpeXV49T/JwqaL0GpzOJz54+c0kwvXyYQp+ffjo
0SpmErATE7fI+TdyTKZfwei/fSrl1wjXU8lU8p3VlTso2H8B966D/0zEq9kjnjD8
sZ3k6537q7DeXD1d4ODkPNySj/EEguzA/gl8rJF8x2hVVzjmwwF2eRWiHHfuMPpj
fvJjfiFly/yPSwPQ0tQzbnAwPM/Y0TEq+fkjnHX8Ac/D7z0DI/dN+Dx/uw3wR8sY
+nJ4efU769zIc9zjc8xzH9mT7w2DCCDGLc+tidX3q8/egxLwJDu5fPJkNcwL0/cK
14Bz6TvcDnNyJ5MYHCZs9XkdE/9H1C1ye555P1lG43ybTpn/v3XrfwFzPoFQpggI
EQAAAABJRU5ErkJggg==
}

view layout [image dog]
save/png %dog.png dog

18.1 Compression

The code above can be shortened dramatically using the built in REBOL word "compress". Rebol's built in compression and decompression only work with text strings, so the binary embedder program provided earlier must be adjusted as follows to create compressed embedded data:

REBOL [Title: "REBOL Binary Embedder"]

system/options/binary-base: 64
file: to-file request-file/only
if not file [quit]
uncompressed: read/binary file

compressed: compress to-string uncompressed
; Note that the line above converts the binary data
; to a text string, and then compresses it.

editor compressed
alert rejoin ["Uncompressed size:  " length? uncompressed
    " bytes.  Compressed size: " length? compressed " bytes."]

IMPORTANT: To use the compressed version of the data above, you'll need to reverse the text-binary conversion after decompressing it. To do that, use the following code:

to-binary decompress {compressed data}

So, using the variables above, the following two lines of code display the same image:

view layout [image load uncompressed]
view layout [image load to-binary decompress compressed]

Here's a complete example demonstrating the size difference between raw and compressed embedded data:

; Here's an uncompressed embedded image:

image-uncompressed: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAP8AAAEsCAIAAACDt/KoAAAAE3RFWHRTb2Z0d2Fy
ZQBSRUJPTC9WaWV3j9kWeAAAB9FJREFUeJzt3bGRHEkQBMETDaKd5v8UmlmzIaoz
MGtZ4QLUIBtBLXM//0lb/bz9D5BeY/3ay/q1l/VrL+vXXtavvaxfe1m/9rJ+7WX9
2sv6tZf1ay/r117Wr72sX3tZv/ayfu1l/drL+rWX9Wsv69de1q+9rF97Wb/2sn7t
Zf3ay/q1l/VrL+vXXmD9P1ICmCh4WkoAEwVPSwlgouBpKQFMFDwtJYCJgqelBDBR
8LSUACYKnpYSwETB01ICmCh4WkoAEwVPSwlgouBpKQFMFDwtJYCJgqelBDBR8LSU
ACYKnv7w+1f8W+fyP307B+a8MxA8Xf92Dsyx/jnjIPQPvD/xixn8G4wjqH/g/Qnr
r42jfuD9CeuvjaN+4P0J66+No37g/Qnrr42jfuD9CeuvjaN+4P0J66+No37g/Qnr
r42jfuD9CeuvjaN+4P0J66+No37g/Qnrr42jfuD9CeuvjaN+4P0J66+No37g/Qnr
r42jfuD9CeuvjaN+4P0J66+No37g/Qnrr42jfuD9CeuvjaN+4P0J66+No37g/Qnr
r42jfuD9CeuvjaN+4P0J66+No37g/Qnrr42jfuD9CeuvjaN+4P0J66+No37g/Qnr
r42jfuD9CeuvjaN+4P0J66+No37g/Qnrr42jfuD9CeuvjaN+IHi6/u0cmGP9c8ZB
6B8Inq5/Owfm9Nf/56/ftHP54T8szoFBDwPBRMHT3/F28W8dDgyyfuTt4t86HBjU
X780ACYKnpYSwETB01ICmCh4WkoAEwVPSwlgouBpKQFMFDz94fy8Ff/Ww4908W8d
Dgx6ZyB4uv7tHJhj/XPGQegfCJ6ufzsH5lj/nHEQ+geCp+vfzoE51j9nHIT+geDp
+rdzYI71zxkHoX8geLr+7RyYY/1zxkHoHwiern87B+ZY/5xxEPoHgqfr386BOdY/
ZxyE/oHg6fq3c2CO9c8ZB6F/IHi6/u0cmGP9c8ZB6B8Inq5/OwfmWP+ccRD6B4Kn
69/OgTnWP2cchP6B4On6t3NgjvXPGQehfyB4uv7tHJhj/XPGQegfCJ6ufzsH5lj/
nHEQ+geCp+vfzoE51j9nHIT+geDp+rdzYI71zxkHoX8geLr+7RyYY/1zxkHoHwie
rn87B+b01//K37mPf+twYNDDQDBR8PR3vF38W4cDg6wfebv4tw4HBvXXLw2AiYKn
pQQwUfC0lAAmCp6WEsBEwdNSApgoeFpKABMFT384P2/Fv/XwI138W4cDg94ZCJ6u
fzsH5lj/nHEQ+geCp+vfzoE51j9nHIT+geDp+rdzYI71zxkHoX8geLr+7RyYY/1z
xkHoHwiern87B+ZY/5xxEPoHgqfr386BOdY/ZxyE/oHg6fq3c2CO9c8ZB6F/IHi6
/u0cmGP9c8ZB6B8Inq5/OwfmWP+ccRD6B4Kn69/OgTnWP2cchP6B4On6t3NgjvXP
GQehfyB4uv7tHJhj/XPGQegfCJ6ufzsH5lj/nHEQ+geCp+vfzoE51j9nHIT+geDp
+rdzYI71zxkHoX8geLr+7RyYY/1zxkHoHwiern87B+ZY/5xxEPoHgqfr386BOf31
v/J37uPfOhwY9DAQTBQ8/R1vF//W4cAg60feLv6tw4FB/fVLA2Ci4GkpAUwUPC0l
gImCp6UEMFHwtJQAJgqelhLARMHTH87PW/FvPfxIF//W4cCgdwaCp+vfzoE51j9n
HIT+geDp+rdzYI71zxkHoX8geLr+7RyYY/1zxkHoHwiern87B+ZY/5xxEPoHgqfr
386BOdY/ZxyE/oHg6fq3c2CO9c8ZB6F/IHi6/u0cmGP9c8ZB6B8Inq5/OwfmWP+c
cRD6B4Kn69/OgTnWP2cchP6B4On6t3NgjvXPGQehfyB4uv7tHJhj/XPGQegfCJ6u
fzsH5lj/nHEQ+geCp+vfzoE51j9nHIT+geDp+rdzYI71zxkHoX8geLr+7RyYY/1z
xkHoHwiern87B+ZY/5xxEPoHgqfr386BOdY/ZxyE/oHg6fq3c2BOf/2v/J37+LcO
BwY9DAQTBU9/x9vFv3U4MMj6kbeLf+twYFB//dIAmCh4WkoAEwVPSwlgouBpKQFM
FDwtJYCJgqelBDBR8PSH8/NW/FsPP9LFv3U4MOidgeDp+rdzYI71zxkHoX8geLr+
7RyYY/1zxkHoHwiern87B+ZY/5xxEPoHgqfr386BOdY/ZxyE/oHg6fq3c2CO9c8Z
B6F/IHi6/u0cmGP9c8ZB6B8Inq5/OwfmWP+ccRD6B4Kn69/OgTnWP2cchP6B4On6
t3NgjvXPGQehfyB4uv7tHJhj/XPGQegfCJ6ufzsH5lj/nHEQ+geCp+vfzoE51j9n
HIT+geDp+rdzYI71zxkHoX8geLr+7RyYY/1zxkHoHwiern87B+ZY/5xxEPoHgqfr
386BOdY/ZxyE/oHg6fq3c2CO9c8ZB6F/IHi6/u0cmNNf/yt/5z7+rcOBQQ8DwUTB
09/xdvFvHQ4Msn7k7eLfOhwY1F+/NAAmCp6WEsBEwdNSApgoeFpKABMFT0sJYKLg
aSkBTJQ7LX0569de1q+9rF97Wb/2sn7tZf3ay/q1l/VrL+vXXtavvaxfe1m/9rJ+
7WX92sv6tZf1ay/r117Wr72sX3tZv/ayfu1l/drL+rWX9Wsv69de1q+9rF97Wb/2
sn7t9T/igalLLsvMjgAAAABJRU5ErkJggg==
}

; Here's a compressed version of the same data:

image-compressed: load to-binary decompress 64#{
eJzrDPBz5+WS4mJgYOD19HAJAtL/GRgYdTiYgKzm7Z9WACnhEteIkuD8tJLyxKJU
hiBXJ38f/bDM1PL+m2IVDAzsFz1dHEMq5ry9u3GijKcAy0Fh3kVzn/0XmRW5WXGV
sUF25EOmKwrSjrrF9v89o//u+cs/IS75763Tv7ZO/5qt//p63LX1e9fEV0fu/7ap
7m0qZRIJf+2DmGZoVER5MQiz+ntzJix6kKnJ6CNio6va0Nm0fCmLQeCHLVMY1Ljm
TRM64HLwMpGK/334Hf4n+vkn+1pr9md7jAVsYv+X8Z3Z+M/yscIX/j32H7sl/0j3
KK+of/CX8/X63sV1w51WqNj1763MjOS/xcccX8hzzFtXDwyXL9f/P19/f0vxz4f2
OucaHfmZDwID+P7Hso/5snw8m+qevH1030pG4kr8fhNC4f/34Z89ov+vHe4vAeut
SsdqX8T/OYUCv9iblr++f67R8pp9ukzLv8YHL39tL07o+3pekn1h/dDVBgzLU/d3
9te/Lki4cNgBmA6/lO+J/RPdzty8Rr5y94/tfOxsX6/r8xJK0/UW9vlH93/9oAzR
e09yKIUBVbT9/br/U/m7x6CU98VAAJS2ZPPF/197eEDhtfs9vX9rDzc6/v3qzUyo
nJA/dz76Y77tHw+w3gXlbEMpDKihza/+7/o/c3+DU54tDwsobR2/fXR/qYXBiV8T
t3eDEmpA/d9LDASK0y/tnz+H/Ynmt78E1vti7lAKA6pouxz/X7v+uR045ZFdRE6x
1q21pG7NiSzx1f5R40pvvdNn+oB1P4Onq5/LOqeEJgCemFy1KQgAAA==
}

view layout [image image-uncompressed]
view layout [image image-compressed]

The compressed version of the image data is exactly the same, but much smaller when included in your programs. When you use compression like that, just be sure to include the "to-binary decompress" words when loading the embedded data.

Using the short binary embedder program provided earlier, you can convert any type of file into embeddable text data. Images, sounds, videos, even entire executable programs can be included in your distributed code!

19. Modular Programming and Code Reuse

REBOL provides a fantastic array of easy to use programming tools built right into the language. As you've seen, Rebol's built-in words can accomplish useful "high-level" actions that can be the basis of simple programs. You can even use the interpreter as a powerful little swiss army knife utility that doesn't require any real programming (i.e., as a multiplatform text editor, calculator, email reader/sender, image viewer/editor, command interface for copying/pasting files, ftp uploader, etc.). For larger programs, however, the built in capabilities are merely simple building blocks. To build more complex applications, you need to _create_ new functionality by combining and using the native words, grammatical structures, and simple pieces of code. Language elements are just raw materials that can be put together to achieve more powerful and specific goals. Toward that end, a very important concept in programming is (drum roll...): the reuse of existing code.

Never re-invent the wheel. Reusing bits and pieces of existing code is essential if you want to become a productive programmer. The creation of functions is a basic way to implement code reuse - once you create a new function word to accomplish a given action, you can copy its definition and use it over and over again in your programs. That saves you the trouble of reinventing those actions every time they're needed in your programs. It also reduces the likelihood of introducing errors - old trusted code is less likely to contain bugs if it's already been tested and put to use in a variety of situations.

As demonstrated earlier, the built in word "do" opens and runs REBOL code that's been saved to a text file. You can use it to import existing modules of code, as if that code had been typed into your program. That existing code can contain function and variable definitions, new programmatic structures, and even complete programs of any length and level of complexity. Once those words and definitions have been imported into your program, you can use the included functions and variables as if they're native words in the language. You don't even necessarily need to know how they were created. Try typing in the following code example, and save it to "C:\play_sound.r".

REBOL [title: "play-sound"]

play-sound: func [sound-file] [
    wait 0
    ring: load sound-file
    sound-port: open sound://
    insert sound-port ring
    wait sound-port
    close sound-port
]

The code above creates a new function word "play-sound", which accepts a passed variable filename "sound-file" (that file must be a ".wav" file), and plays the sound through your computer speakers. You don't have to understand how the code works - just type it in and save it to C:\play_sound.r . Now, whenever you want to play a sound, you can include the code in your program:

do %/c/play_sound.r

And use the "play-sound" function just like any other built-in function word (the syntax is "play-sound {sound-file}"):

play-sound %/C/WINDOWS/Media/chimes.wav

Easy, right? You only have to type in the line "do %/c/play_sound.r" once in your program. After that, the word "play-sound" is defined, and you can use it wherever you need:

alert "Here's a sound:"
play-sound %/C/WINDOWS/Media/chimes.wav

alert "And here's another sound:"
play-sound %/C/WINDOWS/Media/chord.wav

alert "Now try choosing a .wav file from your hard drive:"
play-sound to-file request-file/file %/C/WINDOWS/Media/tada.wav

This whole concept becomes much more useful with the realization that (final drum roll...): You don't have to write everything yourself! There exists a community of developers around the world working to create useful pieces of code. Finding and learning to use modules of code, functions, and complete dialects created by other programmers will remain at the heart of your initial learning process, and will continue to play an important role in your efforts as a proficient developer.

Many self contained modules of REBOL code have been created to extend the built-in abilities of REBOL, and that list continues to grow as the language matures. Learning to use dialects and parts of programs created by others will make you a much more capable programmer. Existing modules of code can help you do high level, complex, and specific things with ease, so that you don't have to start from scratch in every programming effort.

In that way, learning to program is a lot like learning to use other types of technology that exist in our society. To call your friend on the phone, for example, you don't need to reinvent the telephone and all its electronic components. You don't need to manufacture any of those items, and you don't need to install miles of cable. You just need to know how to use the existing phone system. Furthermore, you can put that system to use in more complex operations that are functional to you at another level. Business owners rely on the phone to contact clients and employees, without necessarily caring how the system works. They just use it functionally as part of their higher level business goals. Existing code modules can work for programmers in a similar way. They make available high level functions that can be used to build even higher level, specific, and complex applications. You just need to know how to use them.

To reuse code modules, it's essential to first learn the language, so that you can understand code written by other developers. You may need to adjust and extend code written by others, so that it fits your needs more exactly. In most cases, however, you can just learn how to use the words in an imported module or dialect, execute a "do {filename}" command, and you're off and running with a whole new language extention. Here's an example:

The web site http://www.dobeash.com/it/ offers several free extension modules for the REBOL language. They provide a module called "RebGUI", for example, which extends the already powerful GUI syntax built into REBOL ("view layout..."). Using RebGUI, it's possible to easily display graphic widgets that aren't natively possible in REBOL. RebGUI constructs those components from the built in REBOL raw materials, and makes them reusable in your own programs. To use RebGUI, just download the files at the web site above, and unzip them to C:\ . Then type in the following code and save it as C:\rebgui_example.r. You can run it by any of the means described in the previous section ("do %/c/rebgui_example.r", etc.):

REBOL []

do %\c\rebgui.r

display "Grid" [
    table #WH options [
        "Day" left .5 "Time" left .3 "Name" left .3
    ] data [
        Monday 9:00 "John" Tuesday 9:30 "Jane" Wednesday 10:00 "Bob"
    ]
]

do-events

The code above makes use of some new commands that aren't part of the native REBOL language. Specifically, the functions and variables "display", "table", "#WH", "options", and "data" are defined in the rebgui.r file, and they add new functionality to the REBOL language. Using those words, as defined in rebgui.r, the above code displays the given data block [Monday 9:00 "John" Tuesday 9:30 "Jane" Wednesday 10:00 "Bob"] in a resizable GUI display that can be automatically sorted by clicking on column headers in the GUI. That type of display is a common requirement in modern programs that display lists of data ("database" applications), so the added commands are a welcome addition to the REBOL language. RebGUI contains a broad collection of additional functions that are useful in building intricate GUIs. To use them, you must first understand basic REBOL syntax, and then learn how to use the RebGUI language extensions within that syntax. Once you've done that, you can simply include the "rebgui.r" file in your programs, and use those language extensions as if they're part of the language. The key is to understand that RebGUI commands are built from native raw materials in the REBOL language, and to use them, all you need to do is import rebgui.r using the "do" command.

NOTE: rebgui.r is packaged with several additional files, which are in turn imported within the rebgui.r code. Those files contain "lower level" code that actually define the new words and grammar that make up RebGUI itself. They contain code that must all stay together with rebgui.r. Just as the above program is not complete without the included rebgui.r file, rebgui.r itself is not complete without its included files. As you begin to create longer and more complex programs, your source code will often consist of many separate source files tied together to make up a whole program. Managing and remembering which included files are required in your programs becomes more of an obligation as you build applications of greater complexity.

Here are some web links containing free modules that can help you accomplish useful programmatic tasks in Rebol:

http://www.hmkdesign.dk/rebol/list-view/list-view.r - a powerful listview widget to display and manipulate formatted data in GUI applications. Perhaps the single most useful additional to the REBOL GUI language.

http://www.dobeash.com/it/rebdb/ - a database module that lets you easily store and organize large amounts of data using the "SQL" database language. There's also a spell checker module that can be included in your programs.

http://www.rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=rebzip.r - a module to compress/decompress zip formatted files.

http://www.colellachiara.com/soft/Misc/pdf-maker.r - a dialect to create pdf files directly in Rebol.

http://softinnov.org/rebol/mysql.shtml - a module to directly manipulate mysql databases within REBOL. Very useful for web programming.

http://www.rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=menu-system.r - a dialect to create all types of useful GUI menus in REBOL. A later chapter is dedicated to this topic.

http://softinnov.org/rebol/uniserve.shtml - a framework to help build client-server network applications

http://www.rebol.net/demos/BF02D682713522AA/i-rebot.r http://www.rebol.net/demos/BF02D682713522AA/objective.r and http://www.rebol.net/demos/BF02D682713522AA/histogram.r - these examples contain a 3D engine module written entirely in native REBOL. The module lets you easily add and manipulate 3D graphics objects in your REBOL apps.

http://web.archive.org/web/20030411094732/www3.sympatico.ca/gavin.mckenzie/ - a REBOL XML parser library.

http://box.lebeda.ws/~hmm/rswf/ - a dialect to create flash (SWF) files directly from REBOL scripts.

http://www.rebolforces.com/articles/tui-dialect/ - a dialect to position characters on the screen in command line versions of Rebol.

http://www.rebol.net/docs/makedoc.html - converts text files into nicely formatted HTML files. This tutorial is written and maintained entirely with the Makedoc tool.

http://www.rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=layout-1.8.r - a simple visual layout designer for REBOL GUI code.

http://www.rebol.org - the official REBOL library - full of many additional modules and useful code fragments. The first place to look when searching for REBOL source code.

Pieces of code aren't always intended strictly for modular reuse, but can be found within published open source programs. Searching for useful code sections within programs released to the public is a vital way to save time and programming effort, and to improve your capabilities as a developer. A great deal of potential work in any conceivable programming domain has already been accomplished by developers around the world. Always check for functionalities you need in related programs written by other programmers. If the examples you find are well commented and organized, you'll likely be able to reuse some of the code with limited effort. (Be on the lookout especially for useful function word definitions).

A list of Internet resources containing available source code has been included at the end of this tutorial. The programs available at those sites are a gold mine of existing code, necessary for productive development work.

19.1 Using External Programs as "Modules":

Remember that data is often interoperable between existing programs. Once data has been saved to a storage medium, you can pass it to other tools. The built-in REBOL word "Call" allows you to run other programs on your computer. It provides a variety of options to send command line parameters and deal with output from those programs. Using "Call", you can execute all of the built in "shell" commands included in your computer's operating system (i.e., DOS and Unix commands). You can even embed and use entire premade applications to help manipulate data in your REBOL programs. The example below opens Windows' Notepad to edit the "rebgui_example.r" text file created earlier:

call "notepad.exe c:\rebgui_example.r"

This next example opens Windows' Paint program to edit an image we downloaded earlier in the tutorial:

call "mspaint.exe c:\bay.jpg"

Here's an example that embeds an executable program into the code, decompresses, and writes the program to the hard drive, and then runs it with the call function:

program: load to-binary decompress 64#{
eJztF11sU2X03K4VqJsrkZJp6OzchhFJsx8qDB9od1fHdIO6ds7AgJX2jttyey/p
vWUjJuNnmNhMibzwaCSLi+EBE1ziGIkBGh0BSYTwwAMme9Dk4kgkgSiKcj3nu7es
QrKFhMUQOcn5+c7fd875+vXe27FJAg4AbIiGAQwWIwZMEbqTcmODN5xRdmRi6aoy
Z83YogngLlaNtV+s6kV7q9KelHeu9LYqQTXt7e/v97UqLcLuqKJIvriShnAIoJ0r
gXvPn+StlDAF5dyzHLwAdlw4TZ1Mm7oQvWDu7jKLslsxBc4KQ30bb9bMHF3F/D5j
MFAHEIbHD+cwb88s9riSEIjvK7EKogZs//bxAvQmYlqM5JsOUwHPWFgEAYDTvqTp
eYdy1Fn5Sh/O96h9nLrrDcD4IpQm7UOkWL/nt6MlqMvxrkl+GVWS7xqWalzDzqGz
9rbyD5ehpmnl+ezt3M/RSPe7Q9/ajeh5+9Ztm3vKh9xoM7SaimLUR18C2JKf+Kg2
APoJwzDOuiAF+hHU/pHXryObdLyP+y2kEhx7UaLfo0gq/RJa60/n88Ndrpz7FmqG
u5bk3L8zwdWXc0+jdOYXkn4lnYfW++/qOPLyDz7BfH3jTXVnplx949inhPvnSgw/
8RSIHM7P8PdSUYtxlxSkONE+o/u7EkNElMbpcuRKUhTjmLH/iHbDQQ7DHqL77zbh
oQxeRa9duBQHkRj+HnIdr7y/e178AvmmnHt5VQAmaNo59/EZ8QSJAY7EURJvMu2x
KipYj2CaEToYve2eYYiwl4rWY6jN8RWF5XtsuWSyhO7aJG8XXQFkNdWYIqIHK8nH
8FOSFJMoteEfZfQEo1SNCPCW2/BTjWK1uXkp9dDDegjrDqpkAUtiJhNp4ma3qUrx
MG6dqkyFMQ2ExQmaxgU2c/07D2ZJsCz3Q68Xh76Cvac2pZwi8jCO8rIZd4jielmc
uHxmsEMe1vMBZJf0YY8Pda95yH5p+tWrI86XMZbTE5a1gVlXFKyryeowp0Cy4Wf+
hdSrWGp26N008hW4XnS6/OBS7MnUVHoK0osoTV+22qF56c95qKdtZBzB66J/imSc
/Rmsg/KDdHFbA9O3RrZWByD/qPf1KTCwze3y2KCbn9vnP4ExoItiwr11zvncqq6+
oXGV//XVa5qCzXxL6M3ZfBfMZyFPBvywgD3FGDjLnGVl83o4T+HJAZ/PFxWTqrcj
GxerHljRqyL9sWXxqU2/nkHki1H4HDkvJeM7vZooeLdnNU2R10K34G1XdgveTmE7
vmv7fNDcFY1u3ABpNa5J6rZd9MouqGpjw6z1GLXn6vDxV/s9o1cYvcroNUanGP2J
UZ3RG4zeZPQ2o3cY/YtRqCdqZ3Qho6WMuhitYHQZ0pr6mRr21Zvv03VFuuMoX0Gd
VqT7BlupKFoXw8eo/8yynUR+HvEa4g3EPxEXYuwSxOWIaxADiGHEBKKGeADxCOIx
a1wXkE81zH/ut0OdG0LtjQ2+hCSBzLUKWoeSyErC+pickIQgfAmhgaSG319xPEvo
ioQ6Ld9D0CL04ddZQuknaxA4W1hRtXeySa0DXWM7BHjDFhHkhLUKYs2cJTcrA0H4
mmtXYgk+m1GVTBBOsVVbXJGDsNTWKexIqpqQ4aWYqgbps4LPCDFNMPcLYXQpldrC
g0bcVHcKcQ220DqyB4PTHYKWScZVgCGsw/LBEgHWsjYLZR2zRTMxWZUwfaFwOAot
SXVXTIuLM9V/ZeuSMw/UxW/s4KOF6W2GNjmp8Uo6rci8ImsZRVLxG+1hZWhgrlv6
/4F/ABcSIgQAEAAA
}
write/binary %program.exe program
call %program.exe

Copy and paste that entire block of code into the REBOL interpreter and run it. It will execute a little demo program, which was written and created by a language entirely unrelated to Rebol.

There are a world of open source applications available to perform just about every specific high level task conceivable. Many are free to use and distribute even in commercial applications (see http://sourceforge.net ). If you can interface with them on the command line, they can be used to help in your own applications - even if you don't know how they were written. This extends the power of the language, but can also restrict it's cross platform usability. For example, if you write a program that calls DOS operating system commands, that program can't be used on a UNIX system.

Whenever you use any executable or code created by another programmer, be absolutely sure to check, and follow, the licensing terms by which it's distributed.

20. A Quick Summary of the REBOL Language

The list below summarizes some key characteristics of the REBOL language. Knowing how to put these elements to use constitutes a fundamental understanding of how REBOL works:

  1. To start off, REBOL has hundreds of built-in function words that perform common tasks. As in other languages, function words are typically followed by passed parameters. Unlike other languages, passed parameters are placed immediately after the function word and are not necessarily enclosed in parenthesis. To accomplish a desired goal, functions are arranged in succession, one after another. Line terminators are not required at any point, and all expressions are evaluated in left to right order, then vertically down through the code. For example, "function1 parameter1 parameter2 function2 parameter function3 (expression that evaluates to a single parameter)" is a valid line that evaluates 3 functions from left to right. Empty white space (spaces, tabs, newlines, etc.) can be inserted as desired to make code more readable. Text after a semicolon and before a new line is treated as a comment. You can complete significant work by simply knowing the predefined functions in the language, and organizing them into a useful order.
  2. REBOL contains a rich set of conditional and looping structures, which can be used to manage program flow and data processing activites. If, while, for, foreach, and other typical structures are supported. More powerful and specific looping structures and conditions such as "forskip", "any", and "all" add to Rebol's flexibility.
  3. Because many common types of data values are automatically recognized and handled natively by REBOL, calculating, looping, and making conditional decisions based upon data content is straightforward and natural to perform, without any external modules or toolkits. Numbers, text strings, money values, times, tuples, urls, binary representations of images, sounds, etc. are all automatically handled. REBOL can increment, compare, and perform proper computations on most common types of data (i.e., the interpreter automatically knows that 5:32am + 00:35:15 = 6:07:15am, and it can automatically apply visual effects to raw binary image data, etc.). Network resources and Internet protocols (http documents, ftp directories, email accounts, dns services, etc.) can also be accessed natively, just as easily as local files. Data of any type can be written to and read from virtually any connected device or resource (i.e., "write %file.txt data" works just as easily as "write ftp://user:pass@website.com data", using the same common syntax). The percent symbol ("%") and the syntax "%(/drive)/path/path/.../file.ext" are used cross-platform to refer to local file values on any operating system.
  4. Any data or code can be assigned a word label. The colon character (":") is used to assign word labels to constants, variable values, evaluated expressions, functions, and data/action blocks of any type. Once assigned, variable words can be used to represent all of the data and/or actions contained in the given expression, block, etc. Just put a colon at the end of a word, and thereafter it represents all the following actions and/or data. That forms a significant part of the REBOL language structure, and is the basis for it's flexible natural language dialecting abilities. Because anything in REBOL, data or code, can be grouped together and assigned a label, it's easy to organize coding thought and data structure in a way that tends to flow like natural human language thought processes.
  5. Both data and action code are stored in "blocks", which are delineated by starting and ending brackets ("[]"). Blocks can contain data of any type: groups of text strings, arrays of resource locators, collections of related binary data, collections of actions (functions), other enclosed blocks, etc. Data items contained in blocks are separated by white space. Blocks can be automatically treated as lists of data, called "series", and manipulated using built-in functions that enable searching, sorting, ordering, and otherwise organizing the blocked data. Data and function words contained in blocks can be evaluated (their actions performed and their data values assigned) using the "do" word. New function words can also be defined using the "does" and "func" words. "Does" forces a block to be evaluated every time its word label is encountered. The "func" word creates an exectuable block in the same way as "does", but additionally allows you to pass your own specified parameters to the newly defined function word. You can "do" a module of code contained in a text file, as long as it contains the minimum header "rebol[]". Blocks/series are a simple, freeform data structure that can be used to store any type of information or code. The fact that data and code can be organized using one ubiquitous syntactic structure is another main reason REBOL is easier to use than other programming languages.
  6. The syntax "view layout [block]" is used to create basic GUI layouts. You can add graphic widgets to the layout simply by adding widget identifier words to the enclosed block: "button", "field", "text-list", etc. Color, position, spacing, and other facet words can be added after each widget identifier. Action blocks added immediately after any widget will perform the enclosed functions whenever the widget is activated (i.e., when the widget is clicked with a mouse, when the enter key pressed, etc.). Path refinements can be used to refer to items in the GUI layout (i.e., "face/offset" refers to the position of the selected widget face). Those simple guidelines can be used to create useful GUIs for data input and output, in a way that's native (doesn't require any external toolkits) and much easier than any other language.

Because of the simple GUI syntax, the remarkably uncluttered general coding syntax, and the single ubiquitous block data structure, REBOL programs can be much shorter, simpler, and more powerful than comparable code in any other language (see http://www.rebol.com/oneliners.html).

20.1 Using Rebol's Built-In Help

Typing "help (any word)" at the REBOL console prompt will display required syntax for any built-in REBOL function. The word "what" lists all built-in words. Together, those two words provide a reference that's sufficient for most situations in which you need syntax help - without any external guide. "Help system" displays the contents of the REBOL system object, which contains many important settings and values. You can explore each level of the system object using path notation (i.e., "editor system/view/VID/vid-styles"). The REBOL "desktop" that appears by default when you run the view.exe interpreter can also be used as a gateway into a world of "Rebsites" that developers use to share useful code. Surfing the public rebsites is a great way to explore the language more deeply. All of the code in the rebol.org archive, and much more, is available on the rebsites. It's amazing what you can do with that little view.exe file!

21. 8 Complete REBOL Programs For You To Study

The following programs use a number of techniques explained throughout the tutorial. They demonstrate how pieces of code can be put together to construct complete applications, and they provide some guidance as to how basic language building blocks can be assembled for useful purposes. There are detailed line-by-line explanations of each program included in the comments (it's amazing how short these programs are without the comments!). Studying these examples is perhaps the most valuable part of the tutorial.

A downloadable package of all the demo programs is available at: http://musiclessonz.com/rebol_tutorial_examples.zip. The zip file contains screenshots, separated source code, and packaged executables (clickable ".exe" files) of each example. The XpackerX XML files used to create each executable are also included.

NOTE: In order to fit the code examples within the width of a web page, certain formatting options have been introduced. Long lines of code have been shortened, so they they don't get chopped off when printed. The built in word "trim" has been used to clean long strings of text that are written on separate lines.

mystring: trim {
    This string}

is the same as:

mystring: "This string"

"Join" and "rejoin" have also been used to join together long sections of text:

join "All " [
    "the "
    "same "
    "line of text."
]

is the same as:

"All the same line of text."

Blocks have also been written onto multiple lines where necessary:

myblock: [This
block
]

is the same as:

myblock: [This block]

To run the example programs in this section, you can use any of the methods described earlier: type or copy/paste the code into the REBOL interpreter, save the program as a text file and run it using "do {filename}", install the REBOL interpreter on your computer and click the saved code file (this is the easiest way), use XpackerX to package and distribute it as an executable, etc.

21.1 Little Email Client

The first example is a complete graphical email client that can be used to read and send messages. The code is heavily commented to provide line-by-line explanations of how each element works:

REBOL [Title: "Little Email Client"] 
; (every program requires a minimum header)

view layout [
        ; The line above creates the GUI layout.
    h1 "Send Email:"
        ; The second line adds a text label to the GUI.
    address: field "recipient@website.com"
        ; This line creates a text entry field, containing
        ; the default text "recipient@website.com".  It assigns
        ; the variable word "address" to the text entered here.
    subject: field "Subject" 
        ; another text entry field for the email subject line
    body: area "Body"
        ; This creates a larger, multiline text entry area for
        ; the body text of the email.
    btn "Send" [
            ; A button with the word "send".  The functions
            ; inside this action block are executed whenever
            ; the button is clicked.
        send/subject to-email address/text body/text subject/text
            ; This line does most of the work.  It uses the 
            ; built-in REBOL word "send" to send the email.  The
            ; send function, with its "/subject" refinement
            ; accepts three parameters.  It's passed the current
            ; text contained in each field labeled above
            ; (referred to as "address/text" "body/text" and
            ; "subject/text").  The built-in "to-email" function
            ; ensures that the address text is treated as an
            ; email data value.
        alert "Message Sent."
            ; alerts the user when the previous line is complete.
    ]
    h1 "Read Email:"
        ; Another text label
    mailbox: field "pop://user:pass@website.com"
        ; Another text entry field.  The user's email account
        ; info is entered here.
    btn "Read" [
            ; An additional button, this time with an action
            ; block that reads messages from a specified mailbox.
            ; It only takes one line:
        editor read to-url mailbox/text
            ; The built-in "to-url" function ensures that the
            ; text in the mailbox field is treated as a url.  
            ; The contents of the mailbox are read and displayed
            ; in the built-in REBOL editor.
    ]
]

Here's the same code, without comments - it's very simple:

REBOL [Title: "Little Email Client"]

view layout [
    h1 "Send Email:"
    address: field "recipient@website.com"
    subject: field "Subject"
    body: area "Body"
    btn "Send" [
        send/subject to-email address/text body/text subject/text
        alert "Message Sent."
    ]
    h1 "Read Email:"
    mailbox: field "pop://user:pass@website.com"
    btn "Read" [
        editor read to-url mailbox/text
    ]
]

21.2 FTP Chat Room

The second example is a simple chat application that lets users send instant text messages back and forth across the Internet. It includes password protected access for administrators to erase chat contents. It also allows users to pause activity momentarily, and requires a username/password to continue ["secret" "password"]. The chat "rooms" are created by dynamically creating, reading, appending, and saving text files via ftp (to use the program, you'll need access to an available ftp server: ftp address, username, and password).

REBOL [title: "FTP Chat Room"]  ; required header

webserver: to-url request-text/title/default trim {
    Web Server Address:} {ftp://user:pass@website.com/chat.txt}
; get the url of a webserver text file to use for the chat.
; The ftp username, password, domain, and filename must be 
; entered in the format shown.

name: request-text/title "Enter your name:"
; get the user's name

cls: does [prin "^(1B)[J"]
; "cls" now clears the screen (explained earlier in the tutorial).

write/append webserver join now [
    ": " name " has entered the room." newline
]
; The line above writes some text to the webserver.
; The "/append" refinement adds it to the existing 
; text in the webserver file (as opposed to erasing
; what's already there).  Using "join", the text 
; written to the webserver is the combined value of 
; {the user's name}, some static text, the current 
; date and time, and a carriage return.

forever [
    current-chat: read webserver 
    ; read the messages that are currently on the webserver,
    ; and assign the variable word "current-chat"

    cls ; clear the screen using the word defined above
    print join "--------------------------------------------------" [
        newline {You are logged in as: } name newline 
        {Type "room" to switch chat rooms.} newline
        {Type "lock" to pause/lock your chat.} newline
        {Type "quit" to end your chat.} newline 
        {Type "clear" to erase the current chat.} newline 
        {Press [ENTER] to periodically update the display.} newline 
        "--------------------------------------------------" newline]
    ; displays a greeting and some instructions

    print join "Here's the current chat text at: " [webserver newline]
    print current-chat 

    sent-message: copy join name [
        " says: " entered-text: ask "You say:  "
    ] 
    ; get the text to send, then check for commands below 
    ; ("quit", "clear", "room", "lock", and [ENTER])
    ; The built-in word "ask" requests some info within
    ; the interpreter.

    switch/default entered-text [
        "quit"  [break] 
                ; if the user typed in "quit", 
                ; stop the forever loop (exit the program)
        "clear" [
            if/else request-pass = ["secret" "password"] [
                write webserver ""] [alert trim {
                You must know the administrator 
                password to clear the room!}
            ]
        ]
                ; if the user typed in "clear", erase the 
                ; current text chat.  But first, ask user 
                ; for the administrator username/password 
        "room"  [
            write/append webserver join now [
                ": " name " has left the room." newline]
            webserver: to-url request-text/title/default {New Web 
            Server Address:} to-string webserver
            write/append webserver join now [
                ": " name " has entered the room." newline
            ]
        ]
                ; if the user typed in "room", request a new 
                ; webserver address, and run some code that was 
                ; presented earlier in the program,
                ; using the newly entered "webserver" variable, 
                ; to effectively change chat "rooms". 
        "lock" [
            alert trim {The program will now pause for 5 seconds. 
                You'll need the correct username and password 
                to continue.}
            pause-time: now/time + 5  
            ; assign a variable to the time 5 seconds from now
            forever [if now/time = pause-time [
                ; wait 5 seconds
                while [request-pass <> ["secret" "password"]] [
                    alert "Incorrect password - look in the source!"
                    ]
                ; don't go on until the user gets the password right.
                break
                ]
            ]
            ; exit the forever loop after 5 seconds have passed
        ]
    ] [if entered-text <> "" [
            write/append webserver join sent-message [newline]]]
      ; default case: as long as the entered message is not 
      ; blank ([Enter]), write the message to the web server 
      ; (append it to the current text)
]
; when the "forever" loop is exited, do the following:
cls print "Goodbye!" 
write/append webserver join now [
    ": " name " has closed chat." newline]
wait 1

The bulk of the program runs within a "forever" loop, and uses a conditional "switch" statement to decide how to respond to user input. This is a classic structure that can be adjusted to match a variety of generalized situations in which the computer repeatedly waits for and responds to user interaction.

21.3 Looping Through Data

One of the most important applications of loop structures is to step through lists of data. By stepping through elements in a block, loops can be used to process and perform actions on each item in a given data series. This technique is used in all types of programming, and it's a cornerstone of the way programmers think about working with tables of data (such as those found in databases). Because many programs work with lists of data, you'll very often come across situations that require the use of loops. Thinking about how to put looping structures to use is a fundamental part of learning to write code in any language. The example below demonstrates several ways in which you'll see loops commonly put to use. Be sure to see case study #5 in the "Real World Examples" section for a GUI version of this code.

; First, a small user database is defined.  It's organized
; into a block structure:  the "users" block contains 5 
; blocks, which each contain 5 items of information for
; each user.  Blank items are represented with empty quotes.  
; Note that each block is split onto two lines, so that 
; they fit onto this web page (they don't need to be split
; this way in normal use):

users: [
    ["John" "Smith" "123 Toleen Lane"
        "Forest Hills, NJ" "555-1234"]
    ["Paul" "Thompson" "234 Georgetown Place"
        "Peanut Grove, AL" "555-2345"]
    ["Jim" "Persee" "345 Portman Pike"
        "Orange Grove, FL" "555-3456"]
    ["George" "Jones" "456 Topforge Court"
        "Mountain Creek, CO" ""]
    ["Tim" "Paulson" "" 
        "" "555-5678"]
]

; This program does not have a GUI.  Instead, it's a text
; based "console" program.  Since there's no GUI, we need
; to format the output so that it's got a nice layout on the
; screen.  Here's a little function that uses a loop to draw
; a line.  It prints 65 dashes next to each other, and then
; a carriage return.  We'll use those lines to help print 
; nicely formatted output:

draw-line: does [loop 65 [prin "-"] print ""]

; Note that this is not the most efficient way to draw a line
; of characters, because the program needs to run through
; the loop every time a line is drawn.  You'll see some 
; flicker on the screen every time this happens, because
; the computer has to run through the "prin" function 65 
; times for each line.  Although it only takes a fraction of
; a second on a modern computer, it's still quite noticable.
; It would be faster, instead, to build a block of characters
; once, and then print that block, as follows:
;
;       a-line: copy []
;       loop 65 [append a-line "-"]
;       ; remove the spaces and turn it
;       ; into a string of characters:
;       a-line: trim to-string a-line
;       ; now you can print "a-line"
;       ; anywhere you need it:
;       print a-line
;
; The inefficient code above is left in this example to 
; demonstrate a point about how the coding thought process
; can dramatically effect the performance of programs you
; create.  That's especially true for programs that perform
; complex loops on large lists of data.  The more effecient
; line printing function is implemented in another example
; following this one, to demonstrate the difference in its
; effectiveness.

; Next is a small function that prints out all of the data
; in the database.  It uses a foreach loop to cycle through
; each block of user data, and then it prints a line
; displaying each element in the block (items numbered 1-5 
; in each block).  This creates a nicely formatted display:

print-all: does [
    foreach user users [
        draw-line
        print rejoin ["User:     " user/1 " " user/2]
        draw-line
        print rejoin ["Address:  " user/3 "  " user/4]
        print rejoin ["Phone:    " user/5]
        print newline
    ]
]

; The following code uses a forever loop to continually
; request a choice from the user.  It uses several foreach
; loops to pull information from the data block, and a 
; conditional "switch" structure to decide how to respond
; to the user's request (as in the ftp chat example above, 
; the "switch" inside a forever loop is used - it's a 
; common design in command line programs):

forever [

    ; First, print some nice formatting and display info:

    prin "^(1B)[J"  ; this code clears the screen.
    print "Here are the current users in the database:^/"
    ; The "^/" at the end of the line above prints a newline.
    draw-line

    ; Now print the list of user names.  A foreach loop is 
    ; used to get the first and last name of each user in the
    ; database.  The first name is item 1 in each block, and
    ; the last name is item 2 in each block.  So for each 
    ; block in the database, "user/1" and "user/2" are 
    ; printed:

    foreach user users [prin rejoin [user/1 " " user/2 "  "]]
    print "" 
    draw-line

    ; Now ask the user for a choice:

    print "Type the name of a user below.^/"
    print "Type 'all' for a complete database listing."
    print "Press [Enter] to quit.^/"
    answer: ask {What person would you like info about?  }
    print newline

    ; Now decide what to do with the user's response:

    switch/default answer [

        ; If they typed "all", execute the "print-all"
        ; function defined earlier:

        "all" [print-all]

        ; If they typed the [Enter] key alone (""), print a
        ; goodbye message, and end the program.  Note that
        ; "ask" is used to display the message, instead of
        ; "print".  This allows the program to wait for the 
        ; user to press a key before ending the program:

        "" [ask "Goodbye!  Press [Enter] to end." quit]

        ; If neither of the choices above were selected, the
        ; default block below is executed (this is the last
        ; part of the switch structure):

        ][

        ; This section starts by creating a "flag" variable,
        ; which is used to track whether or not the user's 
        ; choice has been found in the database - the word 
        ; "found" is initially set to false to indicate that
        ; the user name has not yet been found:

        found: false

        ; Next, a foreach loop steps through each user block
        ; in the database:

        foreach user users [

            ; If the entered user name is found in the
            ; database (either the first or last name), the
            ; info for that user is printed out in a nicely
            ; formatted display, and the "found" flag is set
            ; to true.  The "rejoin" action is used to join
            ; the first name and last name, and is used in
            ; conjunction with the "find" action to check
            ; whether the user's answer matches any part of
            ; the names in the database (when you run this
            ; code, try entering single characters, or a 
            ; part of a name, to see what happens).

            if find rejoin [user/1 " " user/2] answer [
                draw-line
                print rejoin ["User:     " user/1 " " user/2]
                draw-line
                print rejoin ["Address:  " user/3 " " user/4]
                print rejoin ["Phone:    " user/5]
                print newline
                found: true
            ]
        ]

        ; If the "found" variable is still false after
        ; looping through the entire user database, then the
        ; user name was not found in the database.  Print a
        ; message to that effect:

        if found <> true [   ; "<>" means "not equal to"
            print "That user is not in the database!^/"]
    ]

    ; Wait for a user response, and then continue again at 
    ; the beginning of the forever loop:

    ask "Press [ENTER] to continue"
]

Here's the entire program without the comments. Try to follow the program flow on your own. NOTE: In this version, the ineffient "draw-line" function is replaced by the suggested "print a-line" routine above. As a result, you'll see a dramatic reduction in screen flicker:

users: [
    ["John" "Smith" "123 Tomline Lane"
        "Forest Hills, NJ" "555-1234"]
    ["Paul" "Thompson" "234 Georgetown Place"
        "Peanut Grove, AL" "555-2345"]
    ["Jim" "Persee" "345 Pickles Pike"
        "Orange Grove, FL" "555-3456"]
    ["George" "Jones" "456 Topforge Court"
        "Mountain Creek, CO" ""]
    ["Tim" "Paulson" "" 
        "" "555-5678"]
]
a-line: copy [] loop 65 [append a-line "-"]
a-line: trim to-string a-line
print-all: does [
    foreach user users [
        print a-line
        print rejoin ["User:     " user/1 " " user/2]
        print a-line
        print rejoin ["Address:  " user/3 "  " user/4]
        print rejoin ["Phone:    " user/5]
        print newline
    ]
]   
forever [
    prin "^(1B)[J"
    print "Here are the current users in the database:^/"
    print a-line
    foreach user users [prin rejoin [user/1 " " user/2 "  "]]
    print "" print a-line
    print "Type the name of a user below.^/"
    print "Type 'all' for a complete database listing."
    print "Press [Enter] to quit.^/"
    answer: ask {What person would you like info about?  }
    print newline
    switch/default answer [
        "all"   [print-all]
        ""      [ask "Goodbye!  Press any key to end." quit]
        ][
        found: false
        foreach user users [
            if find rejoin [user/1 " " user/2] answer [
                print a-line
                print rejoin ["User:     " user/1 " " user/2]
                print a-line
                print rejoin ["Address:  " user/3 " " user/4]
                print rejoin ["Phone:    " user/5]
                print newline
                found: true
            ]
        ]
        if found <> true [
            print "That user is not in the database!^/"
        ]
    ]
    ask "Press [ENTER] to continue"
]

For some perspective, here's a GUI version of the same program that demonstrates how GUI and command line programming styles differ. Notice how much of the data handling is managed by the built-in GUI tools in the language, rather than by homemade loops:

REBOL [title: "User Database GUI Example"]

users: [
    ["John" "Smith" "123 Tomline Lane"
        "Forest Hills, NJ" "555-1234"]
    ["Paul" "Thompson" "234 Georgetown Place"
        "Peanut Grove, AL" "555-2345"]
    ["Jim" "Persee" "345 Pickles Pike"
        "Orange Grove, FL" "555-3456"]
    ["George" "Jones" "456 Topforge Court"
        "Mountain Creek, CO" ""]
    ["Tim" "Paulson" "" 
        "" "555-5678"]
]
user-list: copy []
foreach user users [append user-list user/1]
user-list: sort user-list

view display-gui: layout [
    h2 "Click a user name to display their information:"
    across
    list-users: text-list 200x400 data user-list [
        current-info: []
        foreach user users [
            if find user/1 value [
                current-info: rejoin [
                    "FIRST NAME:  " user/1 newline newline
                    "LAST NAME:   " user/2 newline newline
                    "ADDRESS:     " user/3 newline newline
                    "CITY/STATE:  " user/4 newline newline
                    "PHONE:       " user/5
                ]
            ]
        ]
        display/text: current-info
        show display show list-users
    ]
    display: area "" 300x400 wrap
]

More powerful and specific looping structures and conditions such as "forskip", "any", and "all" add to Rebol's flexibility. For more information, see the REBOL Dictionary in the REBOL desktop folder REBOL->Tools.

21.4 Image Effector

The next application creates a GUI interface, downloads and displays an image from the Internet, allows you to apply effects to it, and lets you save the effected image to the hard drive. In the mix, there are several routines which get data, and alert the user with text information.

REBOL [Title: ""] 
; header is still required, even if a title isn't included  

effect-types: ["Invert" "Grayscale" "Emboss" "Blur" "Sharpen"
                "Flip 1x1" "Rotate 90" "Tint 83" "Contrast 66"
                "Luma 150" "None"] 
; this creates a short list of image effects that are built 
; into REBOL, and assigns the variable word "effect-types" 
; to the block

do %/c/play_sound.r
; The line above imports the simple "play-sound" function 
; created earlier in the tutorial.  For this program to work 
; correctly as it is, the play_sound.r file should be saved 
; to C:\

image-url: to-url request-text/title/default {
    Enter the url of an image to use:} trim {
    http://rebol.com/view/demos/palms.jpg}
; ask user for the location of a new image (with a default
; location), and assign it to the word "new-image"

gui: [

; The following code displays the program menu, using a 
; "choice" button widget (a menu-select type of button 
; built in to Rebol).  The button is 160 pixels 
; across, and is placed at the uppermost, leftmost 
; pixel in the GUI (0x0) using the built-in word "at".  
; The action block for the button contains various 
; functions to be performed, based on the selected choice
; (using conditional "if" evaluations.  This could have
; been done with less code, using a "switch" syntax.  
; "If" was used, however, to demonstrate that there are
; always alternate ways to express yourself in code - 
; just like in spoken language.).

    across 
    ; horizontally aligns all the following GUI widgets, 
    ; so they appear next to each other in the layout 
    ; (the default behavior in REBOL is to align elements 
    ; vertically).
    space -1 
    ; changes the spacing of consecutive widgets so they're
    ; on top of each other
    at 20x2 choice 160 tan trim {
        Save Image} "View Saved Image" "Download New Image" trim {
            -------------} "Exit" [
        if value = "Save Image" [ 
            filename: to-file request-file/title/file/save trim {
                Save file as:} "Save" %/c/effectedimage.png
            ; request a filename to save the image as, 
            ; defaults to "c:\effectedimage.png"
            save/png filename to-image picture 
            ; save the image to hard drive
        ]
        if value = "View Saved Image" [
            view-filename: to-file request-file/title/file trim {
                View file:} "Save" filename
            view/new center-face layout [image load view-filename]
            ; read the selected image from the hard drive 
            ; and display it in a new GUI window
        ] 
        if value = "Download New Image" [
            new-image: load to-url request-text/title/default trim {
                Enter a new image url} trim {
                http://www.rebol.com/view/bay.jpg}
            ; ask for the location of a new image, 
            ; and assign it to the word "new-image"
            picture/image: new-image
            ; replace the old image with the new one
            show picture ; update the GUI display
        ]
        if value = "-------------" [] ; don't do anything
        if value = "Exit" [
            play-sound %/c/windows/media/tada.wav
            quit ; exit the program 
        ]       
    ]

    choice tan "Info" "About" 
        [alert "Image Effector - Copyright 2005, Nick Antonaccio"] 
    ; a simple "about" box  

    below 
    ; vertically aligns successive GUI widgets - 
    ; the opposite of "across"
    space 5 
    ; spread out the widgets some more
    pad 2 
    ; put 2 pixels of blank space before the next widget
    box 550x1 white 
    ; draws a line 550 pixels wide, 1 pixel tall 
    ; (just a cosmetic separator)
    pad 10  
    ; put some more space between widgets
    vh1 "Double click each effect in the list on the right:" 
    ; a big text header for the GUI     
    return 
    ; advances to the next row in the GUI
    across

    picture: image load image-url
    ; get the image entered at the beginning of the program, 
    ; and give it a label

    text-list data effect-types [
        current-effect: to-string value 
        picture/effect: to-block form current-effect 
        show picture
    ]

    ; The code above creates a text-list gui widget 
    ; and assigns a block of actions to it, to be run whenever the 
    ; user clicks on the list.  The block of actions is indented 
    ; and each action is placed on separate line for readability.  
    ; The first line assigns the word "current-effect" to the value 
    ; which the user has selected from the list.  The second line 
    ; applies that effect to the image (the words "to-block" and "form"
    ; are required for the way effects are applied syntactically.  
    ; The third line displays the newly effected image.  The "show" 
    ; word is _very_ important.  It needs to be used whenever a GUI 
    ; element is updated.
]

view/options center-face layout gui [no-title] 
; display the gui block above
; "/options [no title]" displays the window without a title bar 
; (so it can't be moved around), 
; and "center-face" centers the window on the screen

The built-in words included in the image effector program are: rebol[], invert, grayscale, emboss, blur, sharpen, flip, rotate, tint, contrast, luma, none, vh1, across, image, load, text-list data, to-string, to-block, to-image, to-url, value, form, show, return, button, save/png, alert, view/new, center-face, layout, at, now/time, forever, while, request-pass, request-text/title/default, request-file/title/file/save, break, quit, center-face, space, box, pad.

The image effector also imports and uses the "play-sound" module created earlier in the tutorial. It plays a sound when the program is shut down.

21.5 Sliding Tile Game

Here's a simple example that implements a GUI version of the classic sliding tile game ... in only 8 lines of code. The board contains 15 movable pieces and 1 empty space. The point is to rearrange the pieces in inverse numerical order on the grid. A piece can only be moved if the empty space is adjacent to its border. Click on a piece to slide it into the empty space.

REBOL [Title: "Sliding Tile Game"]

gui: [

    ; Define some basic layout parameters.  "origin 0x0" 
    ; starts the layout in the upper left corner of the
    ; GUI window.  "space 0x0" dictates that there's no
    ; space between adjacent widgets, and "across" lays
    ; out consecutive widgets next to each other:

    origin 0x0 space 0x0 across 

    ; The "style" word below allows you to redefine the 
    ; appearance and action characteristics of any built-
    ; in GUI widget.  The section below creates a newly
    ; defined button style called "piece", with an action
    ; block that swaps the current button's position with
    ; that of the adjacent empty space.  That action is 
    ; run whenever one of the buttons is clicked.

    style piece button 60x60 [

        ; The line below checks to see if the clicked button
        ; is adjacent to the empty space.  The "offset" 
        ; refinement contains the position of the given 
        ; widget.  The word "face" is used to refer to the
        ; currently clicked widget.  The "empty" button is
        ; defined later (at the end of the GUI layout).
        ; It's ok that the empty button is not yet defined,
        ; because this code is not evaluated until the
        ; the entire layout is built and "view"ed:

        if not find [0x60 60x0 0x-60 -60x0] 
            face/offset - empty/offset [exit]

        ; In English, that reads 'subtract the position of
        ; the empty space from the position of the clicked
        ; button (the positions are in the form of 
        ; Horizontal x Vertical coordinate pairs).  If that 
        ; difference isn't 60 pixels on one of the 4 sides,
        ; then don't do anything.' (60 pixels is the size of
        ; the "piece" button defined above.)

        ; The next three lines swap the positions of the 
        ; clicked button with the empty button.

        ; First, create a variable to hold the current
        ; position of the clicked button:

        temp: face/offset  

        ; Next, move the button's position to that of the 
        ; current empty space:

        face/offset: empty/offset 

        ; Last, move the empty space (button), to the old
        ; position occupied by the clicked button:

        empty/offset: temp
    ]

    ; The lines below draw the "piece" style buttons onto 
    ; the GUI display.  Each of these buttons contains all
    ; of the action code defined for the piece style above:

    piece "1"   piece "2"   piece "3"   piece "4" return
    piece "5"   piece "6"   piece "7"   piece "8" return
    piece "9"   piece "10"  piece "11"  piece "12" return
    piece "13"  piece "14"  piece "15"

    ; Here's the empty space.  Its beveled edge is removed
    ; to make it look less like a movable piece, and more
    ; like an empty space:

    empty: piece 200.200.200 edge [size: 0]
]

; Display the whole GUI block, centered on the user's screen:

view center-face layout gui

Here's the whole program without any comments, and reduced to a more compact format. It's tiny:

REBOL [Title: "Sliding Tile Game"]
view center-face gui: layout [
    origin 0x0 space 0x0 across 
    style p button 60x60 [
        if not find [0x60 60x0 0x-60 -60x0] 
            face/offset - empty/offset [exit]
        temp: face/offset face/offset: empty/offset 
            empty/offset: temp
    ]
    p "A" p "B" p "C" p "D" return p "E" p "F" p "G" p "H" return
    p "I" p "J" p "K" p "L" return p "M" p "N" p "O"  
    empty: p 200.200.200 edge [size: 0]
]

Be sure to take a break from coding and play a few games :)

21.6 Guitar Chord Diagram Maker

The fifth example is a program that creates, saves, and prints collections of guitar chord fretboard diagrams. It demonstrates some more common and useful file, data, and GUI manipulation techniques:

REBOL [Title: "Guitar Chord Diagram Maker"]

; load embedded images:

fretboard: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAFUAAABkCAIAAAB4sesFAAAACXBIWXMAAAsTAAAL
EwEAmpwYAAAA2UlEQVR4nO3YQQqDQBAF0XTIwXtuNjfrLITs0rowGqbqbRWxEEL+
RFU9wJ53v8DN7Gezn81+NvvZXv3liLjmPX6n/4NL//72s9l/QGbWd5m53dbc8/kR
uv5RJ/QvzH42+9nsZ7OfzX62nfOPzZzzyNUxxh8+qhfVHo94/rM49y+b/Wz2s9nP
Zj+b/WzuX/cvmfuXzX42+9nsZ7OfzX4296/7l8z9y2Y/m/1s9rPZz2Y/m/vX/Uvm
/mWzn81+NvvZ7Gezn8396/4l2/n+y6N/f/vZ7Gezn81+tjenRWXD3TC8nAAAAABJ
RU5ErkJggg==
}

barimage: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAEoAAAAFCAIAAABtvO2fAAAACXBIWXMAAAsTAAAL
EwEAmpwYAAAAHElEQVR4nGNsaGhgGL6AaaAdQFsw6r2hDIa59wCf/AGKgzU3RwAA
AABJRU5ErkJggg==
}

dot: load 64#{
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAACXBIWXMAAAsTAAAL
EwEAmpwYAAAAFElEQVR4nGNsaGhgwA2Y8MiNYGkA22EBlPG3fjQAAAAASUVORK5C
YII=
}

; Gui Design:

; The routine below was copied from 
; http://rebol.com/how-to/feel.html

movestyle: [
    engage: func [face action event] [
        if action = 'down [
            face/data: event/offset
            remove find face/parent-face/pane face
            append face/parent-face/pane face
        ]
        if find [over away] action [
            face/offset: face/offset + event/offset - face/data
        ]
        show face
    ]
]

; With that defined, adding "feel movestyle" to any widget
; makes it movable within the GUI.  It's very useful for all 
; sorts of graphic applications...  If you want to pursue 
; building graphic layouts that respond to user events, learning
; all about how "feel" works in REBOL is very important.  See
; the URL above for more info.

gui: [
    backdrop white
        ; makes the GUI background white
    currentfretboard: image fretboard 255x300
        ; show the fretboard image, and resize it 
        ; (the saved image is actually 85x100 pixels)
    currentbar: image barimage 240x15 feel movestyle
        ; Show the bar image, resize it, and make it movable.
        ; Notice the "feel movestyle".  Thats' what enables 
        ; the dragging.
    text "INSTRUCTIONS:" underline
    text "Drag dots and other widgets onto the fretboard."
    across  
    text "Resize the fretboard:"
    tab 
    ; "tab" aligns the next GUI element with a predefined 
    ; column spacer
    rotary "255x300" "170x200" "85x100" [
        currentfretboard/size: to-pair value show currentfretboard
        switch value [
            "255x300" [currentbar/size: 240x15 show currentbar]
            "170x200" [currentbar/size: 160x10 show currentbar]
            "85x100" [currentbar/size: 80x5 show currentbar]
        ]
    ]   

    ; The rotary button above lets you select a size for the 
    ; fretboard.  In the action block, the fretboard image is 
    ; resized, and then the bar image is also resized,
    ; according to the value chosen.  This keeps the bar size 
    ; proportioned correctly to the fretboard image.  
    ; After each resize, the GUI is updated to actually display 
    ; the changed image.  The built-in word "show" updates the 
    ; display.  This needs to be done whenever a widget is 
    ; changed within a GUI.  Be aware of this - not "show"ing 
    ; a changed GUI element is an easily overlooked source of 
    ; errors.

    return
    button "Save Diagram" [
        filename: to-file request-file/save/file "1.png"
        save/png filename to-image currentfretboard
    ]

    ; The action block of the above button requests a filename 
    ; from the user, and then saves the current fretboard image 
    ; to that filename.

    tab

    ; The action block of the button below prints out a user-
    ; selected set of images to an html page, where they can be
    ; viewed together, uploaded the Internet, sent to a printer,
    ; etc.  

    button "Print" [
        filelist: 
            sort request-file/title "Select image(s) to print:"
        ; Get a list of files to print.
        html: copy "<html><body>"
        ; start creating a block that holds the html layout, 
        ; and give it the label "html".
        foreach file filelist [
            append html rejoin [
                {<img src="file:///} to-local-file file {">}
            ]
        ]
        ; The foreach loop builds an html layout that displays
        ; each of the selected images.  
        append html [</body></html>]
        ; finish up the html layout.  Now the variable "html" 
        ; contains a complete html document that will be 
        ; written to the hard drive and opened in the default
        ; browser.  The code below accomplishes that.
        write %chords.html trim/auto html
        browse %chords.html 
    ]
]

; Each of the following loops puts 50 movable dots onto the GUI,
; all at the same locations.  This creates three stacks of dots
; that the user can move around the screen and put onto the 
; fretboard.  There are three sizes to accommodate the resizing
; feature of the fretboard image.  Notice the "feel movestyle" 
; code at the end of each line.  Again, that's what makes the 
; dots draggable.  

loop 50 [append gui [at 275x50 image dot 30x30 feel movestyle]]
loop 50 [append gui [at 275x100 image dot 20x20 feel movestyle]]
loop 50 [append gui [at 275x140 image dot 10x10 feel movestyle]]

; The following loops add some additional dragable widgets to 
; the GUI.

loop 6 [append gui [at 273x165 text "X" bold feel movestyle]]
loop 6 [append gui [at 273x185 text "O" bold feel movestyle]]

view layout gui

21.7 Listview Database

This example is a simple database program with a GUI interface. It lets you store, search, alter, organize and display data in a way that's useful. This type of application is common in any situation where categorical information must be organized. Although this example handles client schedule information, the techniques it demonstrates can be directly applied to managing information of any type - inventories, customer/vendor lists, payroll/tax records, mp3 collections, photo albums, high score tables in games, etc. This program uses the listview module found at http://www.hmkdesign.dk/rebol/list-view/list-view.r, which is compressed and embedded within the script. The listview module handles all the main work of displaying, sorting, filtering, altering, and manipulating data, with a familiar user interface that's easy to program. Documentation is available at http://www.hmkdesign.dk/rebol/list-view/list-view.html. Learning to use the listview module is absolutely essential if you intend to do any programming that involves information management. It's very simple to learn - you can get the basics in one sitting, and you will use it repeatedly.

The program below creates a default database if one doesn't already exist (a simple text file containing REBOL data blocks), and makes a date stamped backup of the previous file whenever data is saved. There's an additional function that traps the GUI close button to keep the program from being shut down accidentally.

REBOL [title: "Database"]

; The function below watches for the GUI close button, to keep
; the program from being shut down accidentally.  The code was
; adjusted from an example at: 
; http://www.rebolforces.com/view-faq.html

evt-close: func [face event] [
    either event/type = 'close [
        inform layout [
            across
            Button "Save Changes" [
                ; when the save button is clicked, a backup data
                ; file is automatically created:
                backup-file: to-file rejoin ["backup_" now/date]
                write backup-file read %database.db
                save %database.db theview/data quit
            ]
            Button "Lose Changes" [quit]
            Button "CANCEL" [hide-popup]
        ] none ] [ 
        event
    ]
]
insert-event-func :evt-close

; The code below is the list-view.r module in compressed format.
; It's decompressed, and then imported with the "do" command.
; Like any other module, you don't have to understand how it was
; programmed (the uncompressed code is all just native Rebol).
; You just have to include the compressed blob, and learn how
; to use it...

do decompress #{
789CCD3D6B73E3C871DFF52BE6369592F67629905A9F73A6B2A7B2EFECD8E557
2AE738A96221551031142181040D8092B8A9FC97FCD474F7BCBA07038ADA3B3B
E6D56909CCABA7BBA75FD333FCB75FFEE28FBF538B33A5FE54F5B59EAB377FFE
CD77EA77BFF9FE4F933FFFE697FFA17E552CF51B28FD558585FF58575D3F79AC
F4D3650B2F7FBEEFD74D3B578B37BFD6DBB67A50BFAF1E0A5DABDFB6504D6F3B
BD7D9343B56F9BDDA1ADEED63DF47E359D7EF55EC1DF9FAA89FAF5EF7FABBED3
5D75B7C521BE6D75D1EB728EA55F4D665793AB9FC1DBEFE01DBDFAE9643A9B5C
CDE0D59F75DB55CD76AEA697D3CBABAFE1CDEFAA250E3757FF0D0F4AFDE2FBEF
D4C5D3D3D365B383D7CDBE5DEACBA6BDCB6A53ADCB6EBB72621F2E77EBDD5B6A
F5EF9D5645AF0E505F354F5B059378B88492FF81FFFF75DFEE1A1AE05FF456B7
45AD76E68D428C2042D453D5AFD5A6D81ED40AE6B16F75A7564DABF650A7DA2A
C0EA2576F487A6F760FE695D411D40AC827F8BC7A2AA8BDB1A619853F1BAEF77
F32CC379AC370F25E1E9B27CC85A7DDBD499A74426690218D39B4615DB52F5BA
EB9705CCF787765E428FAEF36609DDB59A7509B3DB140FBA6C965738E30DE0F0
F3A7D03FF79FD972DD6FEACF6B3A013AF44D7BF8FCC17D0F1608A4F4AFCDAB39
2D2EA5BED740F71FDC717E969F9D75FDA1AE3EE96C53C02A6BA97F6AB0AA740D
CBE7177FFC4FC38C66E00EAAC25279BE9AD2A32EEFE01109465FD5C294CF9E67
4AAF567A098BF4BCBAD58FB08C974D8DABFBEA27B0CACCFF3975317C4FAF57CD
161A6F9BADA6C75DD1167620FCAA164F6DB1BB99AB555177DA74B4AA8BBB0E10
4480ABBEB8BDD5A56A352C9EAD6AB693FD76D52CF71D30D86EDFDB165AD7B6D3
65FF3CE9F5739FE9B2EAD56D050C6F26ACA08BB22D9E60A8FD7609BD830853C5
B257B05E735745A96AA58ABA560B605FAA714ED3EAD46DDD2C1F6EE85D665EE5
2AB452AC60AE76D5F281D7343DFDF337808B65514FF0C9B7CCCFE4BFA5EE09DB
1E614A358FBA152FF4F6AE4072451301EA6C7B3695FA71AEEE748F2B713537F0
00CAA1CE24FEAECED9836FDF01BB2CD7D4359F69895290BF008080AF80E5F45F
F6456D70A4904DBFCCC284D562BFAD51DA1375F2A8036413A069B66D26DDBA79
A22EF2A886E9710980027A9AD5AAD3D05733A117664C4240668AA2C6BE57F69E
0F80388E60024ED836FDC44E8A8DFEF2E083D999BE6C276B40834145D7172D54
4EBE9EF321635CA841577A5B8A16F3410BF56AA4A5D12611C7BF3FE843CC18B0
0889E06C2CCF93C478C05DA0B463C41BA0B0BF8FEA1FDEFCD7E4CD00A382E506
70C3B2D7CFC089B8FC138CBFD5C349E1A78695D5AF6F469AC420E05250AB0C06
A9BAF584A69A980DA068A42148B663AD4E40B9141E3962D5C93F75DEE97A7566
CA480FE0DB97D48011D62448F1AB9FB2D50633608802D6BF91460A358EB6DF0B
6043B0BDCE6BBDEA9D2AB8AD8BE5C35900F015B29F8404BDE95B90726801BA8A
F4B62DB64102EE8AAA9D29FC7BA5B21A458E6AD50EDE5C0539B39BC1C8801F56
D7955C214CCF891298C2128C54B5707846CB0D2ACE32EC1BFECC184F60D901CB
AEB0EC0ACB2A3024617DF7601201404537C1CEA18383A79B6A197A56FBBAB654
62EA72ABD9235374F8D50F6F5404D307CD63EB50B10245404A4EFD78AA019628
8EC019BB7ECC108AC952FB59505F7E56AC2E6958D7A06D9EEC0AC065411A3B51
543FF22500E353E78139A2D579ADBE47D1D5374DADFA6A37B27AAEDD572B4D08
AEF056D124091898172C0754E1386C59F4C5B5AA9BED1DC8DD667FB70E6D72D1
DEB49D23136BFE7E25D06DBB9F87CE79DD6BB009EAE26015231FC9F58EEC11DB
11A336422FF9023919987355FEC8EC615029308116D404451108F5B0922D4780
83B440003FAA733230ECF7A2063F039E39F1A5FDB4906C01E3A04C4517E48678
6C5D959AC46CCC3F25C377C43DB05AE67EBD9EA64B18ED784F82B194F80029B2
6E5DAD62AD4BA800C0417E832538D934A5464C6CF6755FA991F7B852621BC154
45C64A16401FBA9C80A4DE6FA4EA19DA66D0C0CA5B12876047EF61C2D63C8C20
1AEA3A03F86258A01C8A13255285C31868DA7878BBC41CC6E76B3E40D3C1FB7C
685779741E8518A47B18EEE8349C49319842B2769A8992A0C6CF4EC1396265B3
F0F52AAE6B55DE297523E22FE2719D92EC1A5075963586988BA8899527F48E34
E66BAA5F0DAAC7101DD3D2BC0E703294B6465727C8C1D5B7C302D3E202AE6C7E
1892ECB845878A0298B2AB6EABBAEA0FA0AB9461695528E43F80B05096B76F01
BFBAEFABED9D0279026F9BB6ACB6A0F8B0D527DD36D09769F5D4ECC1695E178F
9A6A3E633B57E9BDEAF6E8D07560F0FD945AD8EEA3468774A30FCF5368D46378
CA34D075B5B15040ABAD06371DF1EA56E135859B6E8B4EDB89811EEF804F6A5D
1FC09167B3E8A82621B8734380BAC3316E01E5A0C43072A61E4D84EFF2F2122A
B560FA57184E6BF5535B010CCD8AC020610CCF6D417643DBEC016DB1A9FF1253
0FC58BF5EA8792084898CD86CD9BBA9C5844CC5F12C5B1F21F679A11971C3FC6
2D77961559FC43AB8ABF068B2A9EB6D3B6A11F7C33D2575C34E82F02DD982FE9
6234188CC2DFEFD4A2450F83D011A935F20CFBAC6CF6A0DE27CB1A293270DA53
96C6005B29854CE4307D53D77C76290FCE2162A40914BD20A89C99B42A85D992
1BF50FF81AD82D273A82F41759095C3BE32E2146A7DC1B442E9EAB5FFDFCDB5F
7277705D8238873F93DB7E6BBEAC2A30DFFDD3B269B7DA96D65D4F7F262B0C2F
764B68A94BEEBB18A7F1C314BD4B034659B5FD01FD38E8D41AC5F89A50C71A8E
0423A7CF53E759CE7E32BDB4FFC7F14933F96B0A21BC574D59BEB7D2E73D88A2
E5C31D08846D6986A5E01C389B2C64A9AEAEE0DF0FF0FFD554CDBE867FA730CA
D7533EA20877960D88A08535F3705E18B76C3BEB037760A417E1C1B4EC76C512
24F9C4F600D2AE2D6D0D2A47D181C47322C60AA9C42B60C06AA9DDD3B2814712
B27327D1B02B29AF4C90883FAF7551A2DFE69E3D19B0F15355F66B78E9FEDD3D
FB57A21E460BE09DFD2714451EA12FE80F3BCD2B3A478EFBF8655BDCF1E755D5
33A601866BEA1ABA2778E6C8E293B536FB49369A0153F2D0AE2786E9A69C04C4
533C9EE011ED31081A0698CF2B8181562013C03D300EB66B99C38F9C402B9A55
5C4FCC3C78BD60557828E815DAD748DAA25B824B087FB60DBEB735820D8E753A
985DADADED8D7641B06B0DB083568E1179479E0CBE5F7AB0668A2D78B4BEA7AF
87EB1B57406818E60012C01802AE529899E5830AC39FBD9E60012337185DDB3E
3069C096779E85C6F55F3CA64D1D74970DC5AB12D06B14B7571782DB802ACDD3
0D0301390CB51217713D182E456BF8DF8AACEED0F57A93B9A2CC44417EFC685A
B1240368821EBE67946AB323BC19B7DFB8B6CDED3DE0FC8BDC3976A6A67E3635
8D00B390D1046CFDC00F2BD68C948799C06351EFB5FD3E658BC58DDE7DCA9DE8
EE3EA9FD0EA071513EC26D2C3C0DC2A109AD0858CCADCE1764368096C9D9F085
E5A028463457914982032455B30B8E0F43B7731545652DB1A901ED019168BE71
B063549F74387ED19B1DA837921F14EE777B466635C05B9884D54EDBE2B1BA33
162A228BA4F699957250DBF0AE208D0B789B31409881890B4BA5459F60E1D97D
C6E2210497F351C3CA083ECB2CC4210DF2199D77AD7E044FE74E7350E8253C67
DD43B523CA077E60CD8C394B3CC06AD21F1B7B22D670ABE1D3DC4D2E5426BEC9
171EC2E45CFC4C8FE286194CE8F3CE5C733581A1830565D5368260FC4EE674BA
165B7D87DE4E34E724FEB6A0EFFE8E118181021B9948CCF39DC40C6B6807409F
F8E638AE60C94B039B0CA15091DBB1B2E2098460E025CDE0117AC4FCEC8834CE
CFA4B35F5A8C0CC085503C6C62FEFDA9241A92274D9131B40A1C48CF00DDF63E
E84A3133E79378960739B64293F9668C18E8AE582CF1419CA524442522E5C637
74C2D020011110C942B22C3986AC85967AF7E68D159F5444115CBB3B445B1F45
DF23CA7153268496785CA971E6B9EB5CA24720250CE2E722DA5AE3CDC7C5C40B
D0D467D7185AA76885896C204B6816F2C0221E1B6A566AB9060B887044639890
09E86B1B13DAB8584EB7D3CB6A552DCF184060FE16308752AF0A30032795AAD4
93B23C073A1BDEA9BB8961B8208E1273F17112CBC481A45110DDF536F76C0C64
DA156D87012BC7ED92F7597E83EFDF75A3BE5153B138AE0943B4606DD8E7B6EA
3168B5D9C36AB8B586EFAEAE2815E9F6A0BE668DCD8C9D0B65FE1502C8E3C95A
74A6EF2FD48587E79DFA1AD44778CE32F5F5DBB7AC9327675B9ACF7EDB5771C0
C186172DFE9F4419347F8241645CE909D0E0464C8A3FC4CB1AD882B2AF688582
58DB217291BD040676BAE8D5BDA74E620FC42DAE0D2C422ACFE6F7A2821FCB32
5E0992B332CB371ECF8FB8DD6F025117B80B43CBD070C05D36C772B76D5A61ED
28A4042D2AF5D1E16C9065E11C38CFE7D76A59EBA28546CB624F296FEA767FF7
45DC8E057A39D72B3961117A11159964B6EBC46B22F9C28A3C63E1C29C6F982F
61340FD9E4B45A2B34B0D9AE2D17246CFD71A33320049D7A87F42A22339FEE22
57D508AC8AC61F59F9CCED65A30201B878E5110BDEC2D77F990F39AC088EDF61
B01C99CD2B68EEDD767833B40622D43DF72D98F699A1309F8EF74D69E311FC6B
0E08D62033C180F121AD8C7DAD0CEC608DE22EAEAD3EA82B8FEF0017D79CC0DD
DA7182D0CDE958C3D0B33E3781015BEAF9CAC9F5A6D5C572AD7618D1B3992DB0
AE761939A038771B0004D599B9E01A59330EA1C695F3DD4596965D18C69F264E
76695D47795918440CF56E071577454DFC30693BE5AED4FAF061DAF9822FC7C0
AB2BD1E7C0D4A0CDFE68044669320DA6ECC549B60AB7DB22B2707EADB6A8E131
C6C22C9484C0712D6C7A99CB9E5B10FF2C5B698EF513670C58F73C9A550A7801
35233B6754D7AB60D5CFB0DC1203B0488065A5AECE07A66A6C63FB67261E1306
32DFB5065A9F47B2292E268BE27829C622BC07C2B229B8B91D7AA8A5B01C1AD6
BE2AF11ABA6C5D0D56C74C0DCA2ED0B18542E3BB7917E62DD4F60F9CF1FD4BF5
CF293770C0E7173878D4390BFFA4C761E2D7B063C213B125B19C5BB61925EBDE
B1E132EBBD8761596D13B2B5A222D40061163A584CD9EAC7C964EA8255655378
EB40A7D5C40260D65F212DD56AE061FD888934D269B983395525F33E141E0950
6BD5AACCA833B1EC2CD020F25ACA44B6CF6BDC47F05E86CC51764ACB7A23CE2D
C128412E144988B1D9B622BE27CC1EAB149652693371298D060B028B88A9055A
F630F7A706B733845AE425C6BB37BEC052A62E79E57E6EF7334838F019216ECD
EEA009AA50DA6E46CE13A2296B8B27836DCC1DED5C90A562C94D481943211548
83EDA1692457AA32BDCCC7177F3ED45B0407C70560DFBA15946492D35E3BBC34
FBCBC45A3030BCC817FC4D6A870FE506470771B3C2A473B4C9B7C5C687992AB5
020F9E6121E82E2C988B7C2CA9908D491645336C151C49088B39EDB954E5B303
E0251F0864E0C0CF21B51C99E88620B89EB9A5684CBE2A9B2FCDA2476A8477C3
DDEDDAD98DACC947DE8B410646FC4FDA755DFC75A7E7E7C418213929371356EF
B55389498BCD5934ABB2769E5B81F9629874189628EEF839A6C4EFE873F4FA4E
B75F2894015FE4E935FB43576BE40E792916BDE7ABD487851856088B769E045D
550A0C5A0CE14C6E949BDE88D783C539E1E0F8663D0E47C2D4DA39E0B3B6434B
874D84362CC54C705B1C98ADB8055F6B7F4B4E8E15B62673890285F495B58141
61C8A26D8B0328A7AAAF800005A6440D5ECD22638D32BF5CF6D884F1F8C0EB34
C30B0C3798028E683610D87E600D5E415738BC7F31A317720551CBC0EBAC7168
C63C2E33865732DC9F357FF7DBEA2F6006D02AF26143E7DC1A86054C7B12F91C
3026EBB0EA541A089B625BEDF6F56093E7FA69ADB78ACE7850ECAE51C56EA781
7188B2A815BAF783F01625B35486951FB6CDD3F6CC420EFA85897F162EC06966
A5D63B91836AB7A4C82B256EA56F4E397F2114987094A9DE95BACACD300C7B16
0B0E0AEC273FB222A8DC34C4D08C33508C056769E31345507C7187D26E72922D
C2359F053A48152F70722660F84B436C332D2F78CCAC5E237D52BA32B299B266
5B9BE88430503B3C1185275C089F0BB40878166FBC8FC71640E5E271F0850F51
F4CE584889AB53064D1C476384907E2292C3BE49C84A41282BD88241CC082199
96A68A35CC04F19B353CDDE4F095E5DE882BECAE38AD2561240A52A38F4CE61B
2333C321D9B7AFA5D57124F9436DCC4B37091461DF44B96DEE0446B949309CA8
C0AD014F4C8BCD084B5F07B1834F82E79C7F4CFD10883E7DF57D96A6776ADE33
BF0181F1FD0B7243003E60CA48A362266BA57C01E60847E39106626E73F58A45
3304C546390C3C0696FF5DF00A160EB93D6525E91A8B1202D1F0FCE974E99B5D
208EEA37BBD74BC401950C70B188526C41052B07C70FA20EC6B72A61C41A0B3C
1897041561973F55800E99A1E2332D7E54996769312EF3E88899957C562187DC
1C2FAEB1D28D211A530586299DE3EF2403F504FC69867E993F5FCDA77C4E824F
47E660D394D3E7426C6ADA8277EA4D2A3735675AC5873C38BF28E3418B7E5C7B
1749A0FAA1813D8B229A381B5676CDCE819C50DB198B2F56CD8758E5CE92B7DD
68B7E36FBC729927E5162FB7070377894805338209E6E15249AC6CBEB6DD58AC
7020D85919559FCDFD1439432797A544BAD3DDE9840E11CF922E1057C3C1BDB3
21AA45225665F3F386F915CC8BE3397C30A0B75D9CCD5236A28AD844BC568F55
B707C8305B2D913CE113C8BD7EB65C8A94B2AC22AD2F80CDC714831B50BAD3E1
D9811F2ABF007320034793D21BD155C90E6FD597917AF2F5CD56A2A5CEB8AB18
9DAD10994700076E56A177679289F91BE33E1063924CB299D150946012B266A0
35E74F244A2C6C16836C5FE34F4B2843BFE65E0A02C2C12642FCA307C04238CD
4891A6CEE3B15302C41F6DB44103494D966EECBC94B2A76305FCB02CDBE37D91
3A7E5222E627D57B847D778C34428A3A1727829920F5960C3757CD5405C924B0
3C1CE597875DB9D52772C1CFAC4752E1166AE9F6932830ADB4DFD190F9DE5671
82392C93E473B99C43028CF062995D938A4A0524CACCFEA32EA5B29D9BDAB380
B854969E9066C22774E17E178E4FE47F380F8345FB7DB0AC6F2656F5A8325F9C
FF61BFB9D56D8EAF29C45FE6A22711715C48F8C73D4F8FD95826C4E717068723
5C0F03B2C930B71F405662684874408E45FC7214027FC0A772795282BD727429
C4F18263F2C5C34B67278008E608050D7E82AA3A37572A78E7909F1C89B33B2C
EF8D4AE3646249A8C73D4F3B10CF32E1D62C71E1EA98DC7701EE792296B452C9
A18CC8853629EEB25FAE69DF05A551A7F63B9EB04467A0CC150B8538CB6A8E25
893CB5E8FA0CFC981B1B245AF3D0353B50951AC31E02E015B99149679FAEA697
F67F26C5DB08B2D13354B3A367A8D8112A09F628C429AC9849B83C92085B5702
5D43608E4D6AB53201EB10DCC413294F32B8ACB7E6100521109F9ED6783E74D7
D487BB66AB3E3C7FA5FEE979F613359B3D7F95AB555DA50C49B29EC879227E08
D728C834109F2ECBF2A1C82202E0D5479EB1131FABA75A0EEFCE619269480381
8C394994D9C5D99E58CD62062FEC8A7788E8A8D08BCDA691CC16FB3B1235DCED
C1E38011910677C888C32CEEC80B6344220FB3A246EE38210618BDD522CA2147
9D1BAE8148DE0171C269DAE86590586B963B45F45F0FB3A7D6E9E4293619BBD3
662E0A434C0A3E1AA49033C0792DC76E897A2E5B5F1CAA4A9E759747CC44963F
26C9B3D291E636E530D4CB1751C32102F00384B2E96A6EB91C3922363AF65866
9CFCA421102B3D7D2FD4E088821837003C02DDF124C3735CA06045C1431A4021
298EAEE3CF1A1F45C6622000C63016BF19EE47BB23E47FDDC5F312C5C74D5099
5278ECCE99780A8610A0EFB2E8CC71546F98DA673ED2F71F643A8AA9D9E4C853
3301D31355F20C88ADB248CC43E87D0B86BBFB6DECEE9FA3CA81DB0EFCD4F867
9810A1F9E75A129C978E98177F3BCD732A070EB8E35416F416BA4F0570A6BA5F
7B3380CE7EC7088A586F516F2F2DB6E309C8EE33B62CFEDF79946E327895C53F
9B82FD320D0C66E360AFE0A7976E6C93A41F879DB22729A849BD6058C408D373
7740DE2DC4936BD22D0E27A2C344E258524874619D2352946206D07B3FCC8769
B4BD65E03D2E6573BE89EE13F03EEB2ACA9D32EEB0BF92C0BF0F171760F254A2
D0A4BE7EA344BEEAF0AE04AC928CD971E20CA0727045E35A4F3631CA29033306
1043B9EB0D4400402C9191D06ADF4C4ABDAC36C06E33958DD44A98FCEC0208B2
3CA2595EAB6F8B7A89592FDA5CE3B36B9B9D6EEB43387333081E5852456E9585
CE15632C510A430F0914CD150B4985065FAA0B8A4C3FE359DF656BC2D4CF6F8F
2EAC231435DC98202815847B30CC55BFF0658C6AF69A8C74D4262294B95380DF
33C0AF2BCA8FB0AFA5D00849F9ED1E434AFA38893BE06F73A42FAE00A9249629
513A28798BDBB96D9039C3C4E4725B2AC83B3B82F4CDCC18F13D6AAE2B41BDB8
F400A545C977885C06B5BD5AC35D28E086CD17C2D4C5E1FDFE4614008B2383D3
7CE1A77AC8D5D9708E073F9DCC9D29E4B790940DB27F8D4A0A99071CB34D666E
88C753769EA3D59B45FE4699FFDE29275C07F3F2B340D9CE261140C0847D363F
D9A078AE00A4F367F9D64CC46FC8FABE9E5F85D735CBE12FAB478CDE8BA5C8DA
BD65180AB75BF20E180CD978378E73176BC72C74BD0B5394160FF3D4E4049A24
C2317ACB639F830D82F9405E244F1B470B3B0A05B3B80D5E0410401B179C815F
0601D3D4360648A58AFAEE3E01F6B8F81C9182A94E16AC5D1215DDA744A4173A
0B44C6432701F10B77330F7B3761B5638327488D49DDD85D9CBA41A3059EC174
EA76E2EC4DD8A484118A43B3EFB31E2F09F2E72EECA52E74EB187D93A7EB1B7F
B9D2867ED801CCF5B6E93ABA4148AB29D83BA5BA70FC3475170B6587B75CC6BC
669F132F27F27288A784C76D4018B9D19EA5552C2E2E6C02E8297B9CEE8AF613
E04C812B6CC7AE557A1B351733879A5359211D3B245630141F1CEAF68047F09F
0740DD70B68700DCC8607593E0EC24E5DDD551C35DED3CA1B96D7A496C100CE8
CC7B4BDC21BE6284FE26DC3E4E3684B938CC98110BFA472C549A5DD43BBA71FC
22226134A4AFDECB532BC5FD8E81B8A82C360C643023664EB33C5DEFC6297039
391866AB31AEB7A42D6912F56C5FC6A43A27F66ED59038CF0941C96E28129917
D7E682CB0298A279C0AD6AB44F2F2F2F55853BFF7A0336F11A89D753B3ED3970
5B4919327D43BC62B1115C9F607132859B2538289260FCDE24016130D68F5E12
42671812DBBDB2B615D78B5C1C334EB88F99BBE8C8E510ECC51E073FABCE12C5
EC3484E1ED4EE281898867F5A6FC24DEC849445256F66A6D33F4E0F021BD662F
A354D944C8E9874FEA07D969C61EB6C684E5D0F064EF1032470F112D9E7F317D
66ED7028BCE77006915BE6369BC25FD5FD6AF56674A6DBAC948A8D25D9BD4279
71C96D35725AFB845FA69189444E8C599E993ECFD2DE5A226BDB8C274639418B
B2081D3F283E32197F4011955F42FBE6128A1CAF47F7911017FFD62C91468F0B
EC452E486DE4452C5CBC0B228ED80E73F030C9F0661E3CC79642328B9C4B562B
F5C55D96B1D2C45B356FF8A1DA4EE3B593FEB64B71F7A5B703F91D98D28CBB56
DD7EB7031292D4A72546DCCFC0323CCE226546B2D958D9D25DD5A11E14B8E094
CE4363A121B8B037ED90187FB257148338DF14DBE24E2BF30B01F67783FCE599
78B930ABBBA4DF1FB387663BB56A9B0D9DD1F189BAAD06389650B969F15663BA
D5A7061FC2A30E80999FE4118CA47C053E3672F646258E69870D032C7A27CE88
F35F352247801F8B26D78588CD6FB35E3A7D11AD5B42F05C3DC8BB0F5E6FF98C
5AB9C376E3762E7422564454780F168C1328713BE62D5F18A4E0C1FA2FA1C0A5
324E822D1F3576D9825E8724F6706C9D207EEF990A20240EB7B42C6E0D0FBF53
F7CE1B7A0790A406F04702FD28C221B3275B1D77A1F598DC3FBDF7A6D4C0FA1F
D47E69F7EF5E0A9141FBA389E1AC9A4D114F148D5DDD6C1B0E5978ACA64D74FD
F88A3628553EAA9897CCE7B4DD539BB23E3EAF0430C7414D6C8D8674F4D12EED
4C928D6D8EFCAB710F7DFE60AAA4CE469F363A8DF31934B5908FD2153FE96D79
FCF09BF1E4EF0354AF8021DDFFA91CC54E2AFCBDD2ECF55CFD778C7190A2FCCA
6E7B4D8379F0E64896A92B3CF91F374F8A6D160F39A60A53E2DB0B5CFB0554D8
F4F2672F4869BC77AD2EEE68F01BB09C283534D13968F987C1F505665493E87C
0AC2EF333A6D3156683610EF33F6E357DE547A318DD97CA2E3E02966F309E90F
6A48CFD13EC7BA1B76319ADC556DC0D0BCB1F31C51BF6E83DB7A4A36AF31C9A1
836447FEE9613D74B4F3767171EF3C2833B2D9AD51C0947114CF7D08507561AA
A72B9D2A901C51937958496CDF67C90C8DF039F2334A7CCC6491BB5A3E59C842
5847BAB8E8FD5D4DF16F28DE7BF9E3EDB5D9576FD1C41E41B45BDDE11ECCD181
D3F61A4D96FD289E885F0C71028BABC1DB9B306376873F37190FACF026E40FEA
CDE5E5E59B14100B391CFBB13E51EDA8CC89B236AC91CD2FED272BD7DADBEA5D
D499F77F702D8B3B9D7C9B68574FB91F8CE5E1597E5AC216B3E89559E3E9081E
17E52C5469F60747829532403A7AC82058FDEC22753301EB52F1CB9AA36E5E19
92F1B93FE4F2BA5CBB74842699DB4BC4A02C908BE46F05BF9C5A2E458BDB90F3
FB08C6F989B6120C1B5F444C11F8D89D3B21A12D31342697AD128ECE77708259
95915E83E18053BE8882F212744A328B21E730F0C4F1D9689C2A0505CCBAAEEC
A55A3CCF7CC10B6437831D0409ACA1E345FAE7CC1C835AE039ACA3BC1D4F182D
45BA50BAEAFD36722EB614019F21203A9029B266483B381C9954AB6BF3FBF12E
B7342D1106EE7733769BDDF806D6F1A5E6CE738CAC37897C374498E2EB174E1E
4FC9F49998D25D7DD8213D8A5ADBCBFEE4DE3A0703E4E387A86151DEEFBB7E98
BF11FF480BDA1FE2AA2096397218141EC766480A8DED26F6D3994E86452AF214
E41DC926FD31456044F590E6324279D795E49DA459586CC12D04AD547438E1A8
707848067FE8C8FEEF8ECA446D2EA6CF138C91316E01A6E22C10DB8D17B3E7E9
EB1AE0B190573598BC6E88B1132DFEABE334BF5DE95E881A2E62166568BC2EC3
5D1E3C58477B73E62F9A16738CDBDA63ABE6ADB928D59FEB8661CDB924CBFA89
7B1DF88657E2AE3FEA810F3308FCBB94499F3EC92BF39B56E2B38FB875257E31
96D241F19761DCC656986F7E969FFD1F598CCF767B840000
}

; The following conditional evaluation checks to see if a
; database file exists.  If not, it creates a file with 
; some empty blocks:

if not exists? %database.db [write %database.db {[][]}]

; Now the stored data is read into a variable word:

database: load %database.db

; Here's the guts of the program.  Be sure to read the 
; list-view documentation to see how the widget works.

view center-face gui: layout [
    h3 {To enter data, double-click any row, and type directly 
        into the listview.  Click column headers to sort:}
    theview: list-view 775x200 with [
        data-columns: [Student Teacher Day Time Phone 
            Parent Age Payments Reschedule Notes]
        data: copy database
        tri-state-sort: false
        editable?: true
    ]
    across
    button "add row" [theview/insert-row]
    button "remove row" [
        if (to-string request-list "Are you sure?" 
                [yes no]) = "yes" [
            theview/remove-row
        ]
    ]
    button "filter data" [
        filter-text: request-text/title trim {
            Filter Text (leave blank to refresh all data):}
        if filter-text <> none [
            theview/filter-string: filter-text
            theview/update
        ]
    ]
    button "save db" [
        backup-file: to-file rejoin ["backup_" now/date]
        write backup-file read %database.db
        save %database.db theview/data
    ]
]

Here's a stripped down version that's completely functional (only 18 lines of code!). It runs the list-view module from an external file on the hard drive. If the module doesn't exist on the hard drive, it's downloaded directly from the web server (instead of embedding it in the code, as above). So, if the list-view module is not distributed with this script, an available Internet connection is required to run it:

REBOL []

if not exists? %list-view.r [write %list-view.r read
    http://www.hmkdesign.dk/rebol/list-view/list-view.r
]
do %list-view.r
if not exists? %database.db [write %database.db {[][]}]
database: load %database.db

view center-face gui: layout [
    theview: list-view 775x200 with [
        data-columns: [Student Teacher Day Time Phone 
            Parent Age Payments Reschedule Notes]
        data: copy database
        tri-state-sort: false
        editable?: true
    ]
    across
    button "add row" [theview/insert-row]
    button "remove row" [theview/remove-row]
    button "filter data" [
        filter-text: request-text/title trim {
            Filter Text (leave blank to refresh all data):}
        if filter-text <> none [
            theview/filter-string: filter-text
            theview/update
        ]
    ]
    button "save db" [save %database.db theview/data]
]

In both programs above, clicking on a column header sorts the data by the selected column, ascending or descending. Clicking the diamond in the upper right hand corner returns the data to its unsorted order. Selecting a row of data with the mouse allows each cell to be edited directly in the listview. Because inline editing is possible, no additional GUI widgets are required for data input/output. For example, no text input fields are needed because all data is entered and displayed directly in the listview. That's a very powerful tool which is useful in a wide variety of situations.

All of the display and data manipulation features in the example above are enabled by the imported list-view module. It makes programming database front ends really easy in REBOL. You cou use the outline above to create a graphical front end for any type of REBOL database application. You could, for example, make some minor alterations in the column headings to manage the contents of the database used in the earlier looping example. In the same way, it's a great tool to begin designing tables and inserting data into any newly conceived database application.

For more information about dealing with lists of data using Rebol's raw tools, see the pages below:

http://compkarori.com/vanilla/display/VID+list+style

http://compkarori.com/vanilla/display/list+examples

Also, be sure to see the links below for examples of how to organize and manipulate data using the native features of REBOL, and some useful third-party modules:

http://www.rebol.net/cookbook/recipes/0012.html

http://www.dobeash.com/it/rebdb/

http://softinnov.org/rebol/mysql.shtml

There are more links to database tools on the rebDB page. Understanding and being able to use such database tools is an important part of creating modern applications of all types.

21.8 Peer-to-Peer Instant Messenger

This example allows two users to connect directly via a TCP/IP network port (a user selected IP address and port setting) to exchange messages. Unlike the FTP Chat Room above, this example requires no third party web server to store or transfer data. Text is sent directly between two computers, across an established network socket connection (on either the Internet or a local network). The application can act as either client or server, depending on the user's selection. To work properly, the server machine needs to have an exposed IP address or an open router/firewall port. The client machine can be located behind a router or firewall, without any forwarded incoming ports. Program operation can be demonstrated on a single computer. For instructions, see the help documentation included in the code.

The underlying code in this example is based on the REBOL cookbook example at http://www.rebol.net/cookbook/recipes/0028.html. See that page for a detailed explanation of how REBOL opens, connects, and sends data across TCP/IP ports. For information about transferring binary files and data directly across a peer-to-peer connection, see http://www.rebol.net/cookbook/recipes/0058.html. The examples there provide a simple model for adding file sharing abilities to your network applications. The techniques demonstrated in the REBOL networking examples form the basis for building non-native protocols, and can be used to send data through serial ports, to control external hardware devices, etc.

For basic information about how to configure routers and network ports for use in this example, see http://portforward.com. That type of configuration is beyond the scope of this tutorial, but is required for every sort of peer-to-peer ("p2p") network application - file sharing, multiuser online games, web cam sharing, instant messaging, web serving, etc. For an even more fundamental explanation about how networks work, and for information about how to configure a typical network setup in MS Windows, see http://com-pute.com/FreeTutorials/Other/NetworkBasics.html.

REBOL [Title: "Peer-to-Peer Instant Messenger"]

connected: false
; This is a flag variable, used to mark whether or not the
; two machines have already connected.  It helps to more 
; gracefully handle connection and shutdown actions throughout
; the script.

; The code below traps the close button (just a variation of 
; the routine used in the previous database example).  It 
; assures that all open ports are closed, and sends a message
; to the remote machine that the connection has been terminated.
; Notice that the lines in the disconnect message are sent
; in reverse order.  When they're received by the other machine,
; they're printed out one at a time, each line on top of the
; previous - so it appears correctly when viewed on the other
; side.

insert-event-func closedown: func [face event] [
    either event/type = 'close [
        if connected [
            insert port trim {
                *************************************************
                AND RECONNECT.
                YOU MUST RESTART THE APPLICATION
                TO CONTINUE WITH ANOTHER CHAT,
                THE REMOTE PARTY HAS DISCONNECTED.
                *************************************************
            }
            close port
            if mode/text = "Server Mode" [close listen]
        ]
        quit
    ] [event]
]

view/new center-face gui: layout [
    across
    at 5x2  ; this code positions the following items in the GUI

    ; The text below appears as a menu option in the upper
    ; left hand corner of the GUI.  When it's clicked, the
    ; text contained in the "display" area is saved to a 
    ; user selected file.

    text bold "Save Chat" [
        filename: to-file request-file/title/file/save trim {
            Save file as:} "Save" %/c/chat.txt
        write filename display/text 
    ]

    ; The text below is another menu option.  It displays
    ; the user's IP address when clicked.  It relies on a
    ; public web server to find the external address
    ; (whatsmyip.org).  The "parse" command is used to 
    ; extract the IP address from the page.  Parsing is 
    ; covered in a separate dedicated section later in 
    ; the tutorial.

    text bold "Lookup IP" [
        parse read http://whatsmyip.org/ [
            thru <title> copy my-ip to </title>
        ]
        parse my-ip [
            thru "Your IP is " copy stripped-ip to end
        ]
        alert to-string rejoin [
            "External: " trim/all stripped-ip "  "
            "Internal: " read join dns:// read dns://
        ]
    ]

    ; The text below is a third menu option.  It displays 
    ; the help text when clicked.

    text bold "Help" [
        alert {
        Enter the IP address and port number in the fields
        provided.  If you will listen for others to call you, 
        use the rotary button to select "Server Mode" (you
        must have an exposed IP address and/or an open port
        to accept an incoming chat).  Select "Client Mode" if
        you will connect to another's chat server (you can do
        that even if you're behind an unconfigured firewall, 
        router, etc.).  Click "Connect" to begin the chat. 
        To test the application on one machine, open two
        instances of the chat application, leave the IP set
        to "localhost" on both.  Set one instance to run as 
        server, and the other as client, then click connect.
        You can edit the chat text directly in the display
        area, and you can save the text to a local file.
        }
    ]
    return

    ; Below are the widgets used to enter connection info.
    ; Notice the labels assigned to each item.  Later, the
    ; text contained in these widgets is referred to as
    ; <label>/text.  Take a good look at the action block 
    ; for the rotary button too.  Whenever it's clicked, 
    ; it either hides or shows the other widgets.  When in
    ; server mode, no connection IP address is needed - the
    ; application just waits for a connection on the given
    ; port.  Hiding the IP address field spares the user some
    ; confusion.

    lab1: h3 "IP Address:"  IP: field "localhost" 102
    lab2: h3 "Port:" portspec: field "9083" 50
    mode: rotary 120 "Client Mode" "Server Mode" [
        either value = "Client Mode" [
            show lab1 show IP
        ][
            hide lab1 hide IP
        ]
    ]

    ; Below is the connect button, and the large action block
    ; that does most of the work.  When the button is clicked,
    ; it's first hidden, so that the user isn't tempted to
    ; open the port again (that would cause an error).  Then,
    ; a TCP/IP port is opened - the type (server/client) is
    ; determined using an "either" construct.  If an error
    ; occurs in either of the port opening operations, the
    ; error is trapped and the user is alerted with a message -
    ; that's more graceful and informative than letting the 
    ; program crash with an error.  Notice that the IP
    ; address and port info are gathered from the fields above.
    ; If the server mode is selected (i.e., if the "mode" button
    ; above isn't displaying the text "Client Mode"), then the
    ; the TCP ports are opened in listening mode - waiting
    ; for a client to connect.  If the client mode is selected,
    ; an attempt is made to open a direct connection to the IP
    ; address and port selected. 

    cnnct: button red "Connect" [
        hide cnnct
        either mode/text = "Client Mode" [
            if error? try [
                port: open/direct/lines/no-wait to-url rejoin [
                    "tcp://" IP/text ":" portspec/text]
            ][alert "Server is not responding." return]
        ][
            if error? try [
                listen: open/direct/lines/no-wait to-url rejoin [
                    "tcp://:" portspec/text]
                wait listen
                port: first listen
            ][alert "Server is already running." return]
        ]

        ; After the ports have been opened, the text entry field
        ; is highlighted, and the connection flag is set to true.
        ; Focusing on the text entry field provides a nice visual
        ; cue to the user that the connection has been made, but
        ; it's not required.

        focus entry
        connected: true

        ; The forever loop below continuously waits for data to
        ; appear in the open network connection.  Whenever data
        ; is inserted on the other side, it's copied and
        ; appended to the current text in the display area, and
        ; then the display area is updated to show the new text.

        forever [
            wait port
            foreach msg any [copy port []] [
                display/text: rejoin [
                    ">>>  "msg newline display/text]
            ]
            show display
        ]
    ]

    ; Below are the display area and text entry fields.  Notice
    ; the labels assigned to each.  The "return"s just put each
    ; widget on a new line in the GUI (because the layout mode
    ; is set to "across" above).

    return  display: area "" 537x500
    return  entry: field 428  ; the numbers are pixel sizes

    ; The send button below does some more important work.
    ; First, it checks to see if the connection has been made
    ; (using the flag set above).  If so, it inserts the text
    ; contained in the "entry" field above into the open TCP/IP
    ; port, to be picked up by the remote machine - if the 
    ; connection has been made, the program on the other end
    ; is waiting to read any data inserted into that port.
    ; After sending the data across the network connection,
    ; the text is appended to the local current text display
    ; area, and the display is updated:

    button "Send Text" [
        if connected [
            insert port entry/text focus entry
            display/text: rejoin [
                "<<<  " entry/text newline display/text]
            show display
        ]
    ]
]

show gui do-events  ; these are required because the "/new"
                    ; refinement is used above.

To actually use this example, the client user needs to know the IP address of the server machine. In practical application, this could be handled by simply emailing the server's current IP address, but that's a pretty clunky solution. As an exercise, try writing a small addition to the code that automatically posts the current server IP address to a consistently available ftp server every time the program is run (the function to obtain current IP addresses is already included). It should only take one line of code. Write another line to automatically download and insert the IP address into the text field when run in client mode, and you've got a fully automated connection mechanism. If you want to get fancy, you could set up an ID schema tied to individual user IP addresses - all updated automatically and selectable at runtime (in a text-list, for example). You could add features to track who's online/offline at any given time, etc.

22. Menus

One oddity about Rebol's GUI dialect is that it doesn't incorporate a native way to create standard menus. The "choice" button widget is a close substitute that's useful in short applications, but it doesn't look or operate in the typical way users expect (the "Image Effector" program above demonstrates how choice buttons can be used to provide menu functionality). Menus are important - they're a fundamental part of standard GUI design, and users tend to look for them intuitively.

Here's a simple prototype that can be included in your programs to provide additional menu functionality:

REBOL [Title: "Simple Menu Example"]

view center-face gui: layout/size [

    at 100x100 H3 "You selected:"
    display: field

    ; Here's the menu.  Make sure it goes AFTER other GUI code.
    ; If you put it before other code, the menu will appear be-
    ; hind other widgets in the GUI.  The menu is basically just
    ; a text-list widget, which is initially hidden off-screen
    ; at position -200x-200.  When an item in the list is 
    ; clicked upon, the action block for the text-list runs
    ; through a conditional switch structure, to decide what to
    ; do for the chosen item.  The code for each option first 
    ; re-hides the menu by repositioning it offscreen (at 
    ; -200x-200 again).  For use in your own programs, you can 
    ; put as many items as you want in the list, and the action 
    ; block for each item can perform any actions you want.
    ; Here, each option just updates the text in the "display"
    ; text entry field, created above.  Change, add to, or 
    ; delete the "item1" "item2" and "quit" elements to suit 
    ; your needs:

    origin 2x2 space 5x5 across
    at -200x-200 file-menu: text-list "item1" "item2" "quit" [
        switch value [
            "item1" [
                face/offset: -200x-200
                show file-menu
                ; PUT YOUR CODE HERE:
                set-face display "File / item1"
            ]
            "item2" [
                face/offset: -200x-200
                show file-menu
                ; PUT YOUR CODE HERE:
                set-face display "File / item2"
            ]
            "quit" [quit]
        ]
    ]

    ; The menu initially just appears as some text choices at 
    ; the top of the GUI.  When the "File" menu is clicked,
    ; the action block of that text widget repositions the 
    ; text-list above, so that it appears directly underneath
    ; the File menu ("face/offset" is the location of the 
    ; currently selected text widget).  It disappears when
    ; clicked again - the code checks to see if the text-list
    ; is positioned beneath the menu.  If so, it repositions
    ; it out of sight.

    at 2x2
    text bold "File" [
        either (face/offset + 0x22) = file-menu/offset [
            file-menu/offset: -200x-200
            show file-menu
        ][
            file-menu/offset: (face/offset + 0x22)
            show file-menu
        ]
    ]

    ; Here's an additional top level menu option.  It provides
    ; just a single choice.  Instead of opening a text-list 
    ; widget with multiple options, it simply ensures that the
    ; other menu is closed (re-hidden), and then runs some code.

    text bold "Help" [
        file-menu/offset: -200x-200
        show file-menu
        ; PUT YOUR CODE HERE:
        set-face display "Help"
    ]
] 400x300

The code above will provide useful and natural looking menus for simple applications. If you need full blown menus with all the bells and whistles, animated icons, appropriate look-and-feel for various operating systems, and every possible display option, a module has been created to easily provide that capability: http://www.rebol.org/library/scripts/menu-system.r. Here's a minimal example demonstrating it's use:

do http://www.rebol.org/library/scripts/menu-system.r
menu-data: [edit: item "Menu" menu [item "Item1" item "Item2"]]
reb-style: [item style action [alert item/body/text]]

view center-face layout/size [
    at 2x2 menu-bar menu menu-data menu-style reb-style
] 400x500

The demo at http://www.rebol.org/library/scripts/menu-system-demo.r shows off advanced features of the module.

Below is an intermediate example with explanations of the most important features. The menu module has been compressed and embedded in the code:

REBOL [Title: "Menu Example"]

; The following line imports the compressed menu module.  

do decompress   #{
789CED7DED761B3792E8EFEBA740343F24AD4D5352EC24C3331E1F59A213258E
E548B233191EDE735A64536A9B6433ECA64566BD8F71DFF7A2AAF0D9F8E82645
27D9DD602672B31B28140A8542A150285C745F9CBF62BD078CA78BECE6B62C3A
8CFD27FE847492CF567378CDF606FBECE8E0E0293BB99D6745992553D69D16E9
58653D1E8F19662DD83C2DD2F9C774F8F881FA7A910E79A97976BD28B37CCA92
E9902D8A94655356E48BF920C537D7D93499AFD8289F4F8A47EC2E2B6F593EC7
7FF345C914AC493ECC46D92001488F58324FD92C9D4FB2B24C876C36CF3F6643
FE50DE2625FF937268E3717E974D6FD8209F0E3328546858507A92961D8DE9FF
AB225BB07C24B11CE4439E7F5194BC8D65C2B1871A92EBFC237C12A45290789A
E66536481FF16C59C1C61C28C032F08056DB48F29A07E3249BA4F3C7319478D5
06B5244ABCF5C30547D38315DB165A8C5A6D821BE683C5249D9689ECDB36EFB6
9C679AB34952A6F32C1917BA67B05F01BAD922ABB1AFD30C0B43A6693249013B
78D68DB9CDC7439E619AEB4C9C266561B5923788E0E7F38223B262D729301D6F
5ACED2E990BF4D81BF386293BC4C19118FC3E09033CEBD16AC11CF44E42AF251
79076C23F9B298A503E0470E2003769D03274E89278B02DBA6205D7D7776C92E
CF5F5EFD7C7CD165FCF9CDC5F9BBB3D3EE297BF10BFFD86527E76F7EB938FBF6
BB2BF6DDF9ABD3EEC5253B7E7DCADFBEBEBA387BF1F6EAFCE252C1DA39BEE410
7630C3F1EB5F58F75F6F2EBA9797ECFC829DFDF8E6D51907CA6BB9387E7D75D6
BD7CC4CE5E9FBC7A7B7AF6FADB478C0362AFCFAF74035F9DFD7876C5F35F9D3F
422CDCF2ECFC25FBB17B71F21DFF79FCE2ECD5D9D52F58F1CBB3ABD750E94B5E
AB1605ECCDF1C5D5D9C9DB57C717ECCDDB8B37E7975D060D3E3DBB3C79757CF6
63F7F431C787E3C0BAEFBAAFAFD8E577C7AF5E55DAAFA09DFFFCBA7B01AD32A9
C05E7439D6C72F5E75B16A68FFE9D945F7E40A1AAA9F4E386D39C2AF1E697097
6FBA2767F0A6FBAF2E6FE6F1C52F8F04F0CBEE4F6F796EFE919D1EFF78FC2D6F
F55E53AAF17E3C797BD1FD115AC34975F9F6C5E5D5D9D5DBAB2EFBF6FCFC143B
E5B27BF1EEEC840315E9F4F8EA18ABE6653919D57B4E86F38B5F000A341049FD
88FDFC5D97BFBF00AA21198EA17D979C1C275766360E8D53C7C0513780BDEE7E
FBEAECDBEEEB932E643B07703F9F5D76F779CF9C5D42060E1BE8F8F331AFFC2D
36037AE42DEF3BDD172F6D167E841DC8CE5EB2E3D3771CD8A92CC5BBFCF24CF0
09D2E3E43B41D4C73BAC2E6175FFF5A0FFE0C1A05CB6B86459F059094673BA2C
F95CF5C0CA57DC26C3FCAE954D929BB483934A0FE69F92E19BBE98DB20E1EB0E
2B5645994EDAF90C655D9BC468EB3A29B44C0B67E9B0AF9EA86CA2CE719E0CF9
EBBFFDE783ECDD8BF38BBB831FBEBDC98F797A7DF9F6B6FBF6863FBD809FC73F
9D1CFF02FF8EBE69FFFD161E5E4CBE7F7571F0D371FBEEB47DFCE6E1CD838F49
7A051F4EFEF5E2ECE77FFDC89F0AF8FDAA7BD73D9ECCEEB0F48B2F2FBEBF7AFB
F6BBC39F7E7A7BF2DB2FDFAEDE24C9F827FEE1ECF5F70F2EBA2FDFA6AFE7C383
5FBBE777EF8FDFBE181E9F9E9E7D7F7AF64BF2EF9FDFBCFF7E74F9EFC5717E74
FD53B6BAFEE1877FBF9AFCFAF6B78B41F7F0EADFE70FDF7ED91D3D78F3F4EFA7
776F57DF9DDCFDF0EDAF372FF2C1CB7CF1F0EED5CDEDEDACFCE1DDE9FBF7DFFE
38FB707EFDD5ABF4F8CDCDC9AFBF7DDF3DBC7C77F9E4E3FBEF6F2E0E8BE3F70F
7EFEF7F7B7C7CB9BF7DF1497072F3ECE9E941F3EBE990C96CBB47BD25EFDB67A
F26A79F8F1F8F8E555FBFCB7BF1F5FBE6FBFBC3DCDAEFFF5DD3552A83B7EF9E0
EAC3E5E2A7C9C9C983FF6AD41FD8AB7697E0AF3EF1C92029789F8F16D301EB7D
4CC68B14DEA405E78B820BF0019F8956B3F479FB2E9F0F59C7CC40C581E55A45
F65BFA5C02D9B948CBC51C2649364A06E96E817918E481D9E46079C0391CBEB0
5E7EFD3E1D945FF4597B9C0F9231E5E1F82EC625AF5F8F2A78DFC1326D7854EF
D59B0E3B3CE069897F6B878F7F3C8911C0EBEE60852D441BAAF0D567E141C504
55154024758536AFCF5F775B45324AE92B02E323349F978345B9432F590FFFF9
82CFDED3F48BBE394293E98AF512AE48521603481F08DB7750981665321DA4AD
7C2451D0D3A3ECA7AB8BB75D968DF8945F66E58A65A8CFCDD35F17D99C4FF25C
AB290AB6078893CA729715E9FEE31D054714939D29B016E57AC0375F08BC8C76
40134CF58151E9E7029AF5691799E519BB01993595F5ED025F5AF946F9623A7C
CE461957CE9050950202A3BEC04D9575A8E6658DA6090B4CD3BBD61014E0B13B
2AA66C31CDA65C911CF36E1B72A52C9D30CAFB78C7ECEB49F2219503FB6396DE
F13F43F8AF4563675DBC3061C33B04FA9A8FB90F5F704EE2150864FB9BC04C87
20E307F9389F77583A1AF16E94FFF2AA801B9AC1B1D96134A2B908071B676DBB
A7D3742C5A8143127E57F8091267DF7972277B809A28204B89038DEF7B8A4282
6F1DD135ED19D768A765CB120756E691CC299A1E000AC9CAD861B36CF0A15A18
B9E21FFFC47FDB30C1B7930157D38953ABFFD9F37D90B6D8D135A4530F1C1F44
8E6887E8A41F3905AAC4E2ED063D3F9B3EA7EF6D415FBE286AD1E3736A0CB604
C59D4B18DD4A408733799195FA253EF9CB60765E2BACBA7C991025EB6DDF18F6
86FC3619C5CABF73CABFE0A8850A1AE866350969A1273E41ADDE2CC9E65F68DC
883B2D44B8202F531A66BA093D374B47FD44BEEA1DB1C33ED5FACCCB4D95C254
CD4355F8A02F7A0EDFB30A8FD9AC00F9841CA8BEE66B47BEDE2BB3B468231D31
5BD1EE2058174AC695D9B6D01E235020DB63CC56034A0EB5FB81BACE87ABF688
ABD91D16C30AB23D866C11088A4E7108ED1A3A213C2E9A920618413617C287B4
D2243F049E2DD0240940F77C14405D8B009CD5A030387F83683A725E57014036
7F619B858385EB1A8279EAF9CEC81681658289C00A83A9CAEC695EB274322B57
CF231C43C32124AF91F72466837C32CB8BB43D4CD35960E603111B991449A1DD
4EDA4B8A1648547660A00A734F7BC55A6CAF48C19467EAE7B5441034DD676D76
B41FD509F79A82F242E93B6FFB81E90B92F543B02D6ABE9E91E915405C094C06
1FFC6A9FCB8638DB4237B626C9FC8398CB08CEBAA9022F99CFF3BB4DE11913B9
42CE525CF4744BC07BE56236AEACAC1452505AB1B5BB4EB1F3B9030D12AC8F43
3AA55E9069306C779E0CB33C32381A0C2F93045C9F48A76C0F5BBBCF1744E371
CB7C31C8E68371CABE5E7EC3BE74190E49CA7CAC1869C0E036057D617B0D8866
311B33CEA669EB2E1B96B7EC887E0C92192B7E5D80091A7FBFCF613380AB82E3
2850C8CBBEE434F96A7978C80E8F964FB59EE5FBCF5E2F6A36DE9CF5B07813DE
2B16D72ED3DD5B02477966968F5737F9342EFAA4D8355A835277C9A52EFBC679
BD0269CA3F1DD648D41858DE5521B04F3E07D887B5F2FFFE44A859DCE1F24872
590C9535D2CE3B002AD69D7B435CF364E56EC16EF38F7C714533165FFDD84C6D
2C442AF345712BE5F9E6A962A0B15EE19ACFBF60EBC227D912C043B4840F1A34
959BB62B488126B5A7E9DD3D90F7266504CA26937498719A8E57B473067866D3
9BFBAF2FFF4A0D53DFB163A0C0DD661552566F0B1E28ED7A31EE482130B509F3
A769369CE5B3166E617BA78E7ECFB51A89DD651800FED902B815C7120E12A891
CFC0D3A11CF26BB5BB5F5305FEBA6715768BA382659C261FD32D08575B26BD02
A81EE9BA98A27C4D87C2EE027E0583319FC65D99E5015BD130EE87677F7BAC6F
2149BB15B2BBE403E9FED854F5AEEF9F418C69D1370FDCC022CE51F90DF36E51
D5B502EAF906AA39171AF968A40C6B6A45105BE6DA393BAC9C2FFC16655F8A82
2D3A86810F467C7B964CC3B047F93C4D06B7ACC8AEC7E0C182206A146F29256B
113689292BD805C45A5E3B6D35C922C2025E5F9DA2EACD3C5FCCF466910024FA
123F468105A4910737AB1347C9B8A8EF456469B3B8BBD8673ECD4FB572FDB597
C36E6A0E89AC5F0DBD4EFEAA0C38AFE685032F19A08F131ADE6D761543729E73
0CF89CC47B89D73E2FC1D348F09E8D4D608216153C63BB1C024D794ABAD81364
3F2AC05DEA8EC876B2F6BA7DED75B24BF048E7892D0E25E6FC03C26596E0AC0E
963FDCA9296EB351793F8D073AD3943A882CBCD4CDE6AFBD8D835CE62C00BF9B
12C69D1B54817A3A6423BFB54F30D6BDE831CC8D49A80F1E88C36865196D2BB1
DD221D8F02EBA5980110F8BFA2CD78F6B1F1C7753207F8E606A0FA6D307DB198
C1C24A7D521F404F11DBB98E30B0D0B037D3B04DB4CBE5B57908AB256E37693E
22DB7A6817D46AA0264868CB524A1E2CDB501930048B516F4CC6422E216377B9
9A3A6D95790B57A935659AEF78421AE58345D19EE62D3504FC23CC1D073652D6
66604C67F9DC88621D23C28E234592095C3B6A752893E0A845C729AE0AD6ED19
43AA33C343AA54DA9CA2BCB513703DB65B1BD7ED36E982462DF66B1B1EB902E9
F7B1BAD880EB4D26751A4388369BA4ED2FE97FFF353B4963EFD25D73143672AF
EAC5B1CF1EB2BD83E521FB0F9129FB2DDD6FD4D0BA65BEF41BF9AC1845ECF790
C06B0B80783C1985CB56C23B852FD6154EE8B1156DB7E9C5051AE130990FDBE4
C165CF43E49965BB66A90553DF193BB019DCDCB72A9EA09A6DC2DB76FA98D076
FCB6F003C74EDEC7E8F5209C4CD4EAE8CF4001AF47DD9F03354C8A587F342281
A495EDED908D5466AD2DDF370D9332D9668772C1B855FE206B1A78447518380C
74D88794FF11BB9BEB57046628F043C7A3571B4EC7CEEC414892BCD4EEB6AC87
361EC2722D932835D702B5A1499512116E7BF0B00B62F01C0AC9FE5A1F035A48
B2E04479BDC8C6436BAA549F775EC037E530F98895F94D8AAA091E6B03854CE9
85300D1686FEE6B167EF24A8D2C0971DE38023CCBF958C70CC0DDFC303429AA7
4536044BFA545781C718C8295D3AAAEFC8021C2B9E7F5A66A32C9DEB221C046F
204DC9827E6611F83CCFF0E00595118674AB556DA21569287CA9EF1AB295AED3
61D63A09DFD3040858DBEF499000062E400DCB303ED8C54D3989E348BAF0BBEB
7461B0E818C04D43869B5F398057F24B0767772743F884BA55086380AF88E1B5
C9CBF55C35897228647C3994B365C7AE36ECC6A9FD297D45BC6E89A603580053
E921D9A9345FBEF7345F7A41FA8AF8D1B0BD238D6E190AE73BDF6E0B328BE1FC
EAB568B9E4D2C508C770310B57CC66B8A57A8B39FEA8AA94A047B0945B974113
6F29CB55D42A5A35B80597E1243087191CF07517D04A66920013D9B42F80CA48
87100CC9B743C784313F9745E23B40F1CB22E5F7EF5F7DB88B0F01DB66571447
96890061A3C02114AC0F5575CCAE99B4DCC3AF8F1E1F7EF5CDE3C3A75F8668D8
2C5167EAF30DC8D8F73401D0211476B83C723A58A02F5611A6AE1EB0DA2A23AD
CB30B0FCDD1D27AB7C51B66843A4CA27AFF0235959CC89D39E0FFD3317BEAD9C
2BA0E37C3859EA4E12B37FCFE4BA2215C06C84F389CFC805D4EEED0ACE619FD8
EE7582FFB45AAD3EDB8BD0D94A519356C71E51F694594DF67E6807B666784B2B
AF6BCC685404952825340BA5D2A24AECAED3CD640E828A94F196719DCC3E3162
11EAB47C56C2AF16A9327BC17A31B7702614C737CB9C5129FCE947D9EFE3063D
BB4B26BE608590CC2EC2ECD2B2429C4A68586A8B2F6DA1DF109978DF4905ABA6
FF20ADDB8790FC8484BEDC2DC6C8BBCD49290A0031C5E35FE4C4E4AFC7FB32BC
C50B498F2F88BC31BD898D2C485AC5219B1296DD801774BD65D2AC525075A84E
3E9C25B29B56DFDB15FB911A0FA1057C725EF56BB0F3EF746E4E9ADE2EA8F030
7FC0BF45DF40489E93FE24E4D927705BC6DFB3A4BC857F3743D95A55C0F97859
5F140A24C2834E1E47A4AB990865D6C320090DCB8876F71A66276AB0DE306F5A
81D4011B648F7F15478D10DEF318A11B1037DE4DE4FEDE000CA4BD08ACFDF8D7
3F1CFE7DBA234A4221C22379EAC5927970731340FB7EA92D04011E1103498040
FCA2408A002912EE270AAC65FB5AA2609371BD81F8587F6CAF293C7E2751103F
70584DF16EBA9F2830607986AAF9F50F87BF4D51609130200ACC4E6235B131B4
6A141007E691C8C34DF46761173484C02E2EC43E9165D718F3750A95F0FCA3CD
788245A09E894125D81F0C88EB5155DA34054137558276A5D9B44163FFB0B62A
2BAB1A807B08693F0C26D26234791ACD6DD8BC8AA93268A51436A2E0F79AAE52
751986CA908D52EE5285BEB3CDD9028C99F72112194B8336D91891B0EA358824
ECB221936C8C4858D5E64402746B26830DB9CD26E6E7E7B8F08C700F6EB01BF1
F93922DC08BF9CC0A67136F8CC5DD86820C7C7F11FDD818D86597C94DDABFBC2
BAFB2E9AE17FD7C94B458EAA99C58C2D20DFC64F8060D81E42C09FEE6504EA89
53479FC481813ABA24B3993C45D1A62867BB9345B948C6F5A76DCCA2782484CA
3714ECE6C9927C345AB3D1587FD41E0769371FB11E98BBE9F88EB5EA1B67C202
DE788CE843427526A938DE950EABAD7C379F42A15D38EE850F2BAE437FC2D35F
EC5383D2A31114C27346F000BE579F1A1C3BEAAF4316D1917229B97D298EE66A
08526C1B191B682BE4E930CAE66A05BE7122270C7099127B6DE423415BA7AAB2
C0618C4AFA4B63D72D361B3ACC8AE47A8C9C9A4EE1A9914A2AF88F803C63124A
C45864A6863B03A1E334FA496D268A09ED41ED10C20CC9F0FDA2103BB7EE16FF
317E65D24B496CF062DC120A273E17BEC6655ECA48A860EF5E4CD221BB5E395B
BE543EB6B9AB1998A2F5E03B30A56342A51813A8569828088F6A8F8481119210
063D010C7A0218F4C461D003C2A0471521B5C199CA5052F57734021D8D4147A3
D05138740C24ECD091F2C42B524610D0420D43F1D1213B2014D208C88300FB72
118FA7BA22D9EC435DAEDB4E8BE27E70DCAC4FA05208A4D5E373773FCE063772
0E1A1AC76C7DD39241C6C3AF96875FB9390CEA4E92A5F19B1E45FCDBDAE05966
40BDC3469DEDA262F42EA2A2198EA9905E1D2F7341F620D789B3126ADF8EF5BE
5A3E610F8D58C6F88DCB551553370226ECD6A45A5F73A87A9D3864CD48A99313
99A59AAAB155ADC2EE17F78D1E77D8475A16E0A36017412BB96BE9A138FF2408
1E72658DB2ADF382661431D28C00701D37AB44133E928AC15B924D1693160642
A68D6D2F91D48068AFCC3A0C88EEFCE32121695F0203333610B4D6DB32418B20
B63121EDC12080923829B4A2AE35280AA18C0CA4318491CB46D4921810270E92
871791C6514CCC5E4020767B2231F3C2F2537A16F5D8BDBCB6EB6B234F8E90BC
D6624E341A73DB51D015F8EDF3454500EB32347CAB2835A7BB3938CDDE7CC844
70C6CA1468026810EF18492163487399E2D168DA4B43A7682FA1359236F84BB5
157F491AE10F2D8DF0A7F60AFC932818C66831085191459AEACB8E4915DE20E2
9ECFA78468C9B1F40B38359E971DB357C2A303321A1DE69DA45446B3335D1E32
23B679B10B9141B54C907B5955F054D37406BB0027BD411A7F6B8DB2162CC1BF
32D49C9F0246610B9860F750614B8E43610B182F6C9077CD89B9D9D8AF889575
457804850AEFC7917DEA10B5716A82747C263065ADEE0A6DB837BAE3CB268557
6661359756253ACDA851FA9B12A663CB1F3545D8AA50DF95C6F2CC112DB84DF8
F843BB3C46EE5348E84205E11A899AF38EBA59045F3AD72B28BF54AF3BBCB08D
0A8BAAD13D5666F2A69499D18952A28A97ED14E321AF976C4DED126FE782771D
89E8E1D1C1F2F09B3EC37C151A19F4305D40232708ACD6ABEFE2ADF76C157DAB
9EAE62D1F355025EF08415F39DB1A242B15356CC77CE4A15734E5A550C1E9C80
557B83E1826C7ACC228ECA61B6C27456E76258D70FD3FC5A925C9CD0984BC682
6F22B6EBCA642F2AC327684E42A57DED8E53CE49E4719BA2953E9F1355DBF0D4
07C00A82385A5039C1E58FA6AEFD275622964AAFF79471A6EA7DC39EF6112E18
D4A0520BC326F1BB03516363D162DDCC91E8B12A602A22240431A8F1D89C7620
386CB0548B7DC94B0582B4064B3DDCA8941743CF590F56E12A8013BA89E3FF40
060AB6431C871C062F9E07E2E8205673349DA29B3FE5C5B7C6AD25F6B3E627E0
AFC750DA137E0FABFE5CC90C2B62C614C34AAD306262A801A69EEB478C202A9C
C5CBCC7B26850E27C568E22D42A153B8D8C9C7543F8268DADDEE3C867A493218
A4EAB455E8829F36E5124D51906E52C2B683873FE869304E93B978C6CBBEC4F3
9F2862C0DA49F3C6321DDC271CA7D5073B976959C02564181D106605DC6B28D8
5EF6387D8C752D4AFE136F3115ABACDD42DEAC8741382D789E73C8BAAE2B3561
E6C0CE2C9FDA1170C073132E7E01FF4D9A2C335ECD4D3A87F9F20D7C848262A9
974C5777B7299F2C304E92B857B59CA7691525445D4CBE045BCFA83B27F051B4
72E83650E3E7B97F06D1D0BC5E352BDADE9D8405FCD5E7FEE157657F8A17C204
E83E370882871A087BF8EB292448D5B890D3146923A192B827098F9E7C66DC3C
BE10E27596F315EBF1CA647FD15917DF1C093371E5804DB59C2D4C5C18428530
8EDAA8F35B6E71EB97405C5CF3719B262AC610142FDAF314C35011059D7A8739
D9213AE6602093431DCA728755C8B036709C7A49F501D0A818D54B043C6FE40F
3A755C96C9E03655D10680B23866D2255CC13CBD41B255224F6100CAAA7A5B49
7AF0426ECFB865FE91ABF4DDEAF885C7215EAC6B45C5AA8EDECA5D7A7EDC7E04
BCF2B98E42C4A7FD39EFDD95D8D04E87C640C66EC7FD4771D59CB8F519DF8B6B
832B446A5FA760C8623B67487A11D44BBC448D9C2EF485B5D6027B3887C3CAF0
2C258F1BD72B1941F8B10A4C7A69839C71799112D01DFC11868967D01C98D43E
71D79B6A1FDB1BA6A364312ECD4064EDC4BE10194E363B4B979F61B170BD6277
B7D900FB749E8E00EB5C459AC093DA31D909CB2CD55B05FB98CDC139C710A6CE
ACA2C40E8D4E0C96698E268AA28ADF604462F8E43EE58B08BEEA11C040F043C1
339EF9B622CB1079D74E459C6217C7106795E2C031AAA1F202486223F8DBC7B6
55841F7E70EB14ECA5EA9CE2DD9D9E3A81A39C3A89CDE0AFB74EFC60D7D916DC
14B20EF62C49AB0F4BC668D777FB4A38136CD64F84BC0B1952B58FAE133E5369
7A19B486AFEB92DB20DB56A8166A49EC36243FC96484936774ADAD12B85E8C22
F7F820116A5DD74C1B9FBC85541BC6372B2AAC9AC1C235DE1618BB0131EF80B0
AB5C91CA5FC53D88AC0EF29F20F6DCE0DA180A75B4D41801418216478AF2F8A2
E738C4097E09D1BBA6878528EFC8320DF0ECC84252A00B3D4CBC65E1B33090FE
EADC353BB7492857E18758150A6CE775FB78A7B9CCA94EAF162550584742F39B
F21E52A030E608A3209CD090732D6279364D89EF02B79EA615CD1A43AE2A65CA
7458CB4A9F82DD64552C181F407FEEA57183056DDD7AB14E07ABD8CF3C2B2D9D
462A30785DF8050F1CB05A95C68CADE1E879121B77E4B4420C23273E8D55ADB1
C1832169C0E6845DE663141D2255C4251999D15137E38BDFCF5A62868373AC25
A8F88B85AF752F538869221C62C56EF38C452474F13F97D06410B23677344A40
E80E7B07CDBA2F9D2DBB4288CE348CC2A4FE9F26FC22E4135828027A68E7D28F
26F520FD7A83A41C54EC6FF730D5DCCF50F359CC34B4655C6FA4F9CB38F3A730
CE8406825F5D263BA7A32CC7D7E0B689860608762FB48219024AEC3A783471D1
875E48F40D60358124BAD30B09BF21520D20C9F5BA1F5221B3D541F21DB278A0
7FBA920678E23A99DB3B65C6DE19EF072D91E8C212E3ABFE666BB855326D96DC
D9610FC4043EE5D3F16ABF66B6A8C2136D35833A4A9906AF6B670A264DD1FD10
9B0B50B2FB906052F0CB6A24284F8A4DA79FC738FF97C4FF4BE263FA5398E371
67138EDE0536CACA5B383685220AF707BF901400162FE1220FB87A9AA9BD3B31
E4387D8AE92E9F7126B3713A819B55866C95965FEC0444A74CBFCF0C4642627B
F398056F0BB399056F0B739A0D6FE399AD492C605F52B39EE91554B910069C3A
F0335C8FD24AEE12D3FA5C9D0E71856FCC83E6955C95C50EDE469AF88471C5BD
41DCD0D20357A8F86C83B68CB1BCDB4BBEABF81CE1D75051E997E62F4A5F8365
653C677F59339AB3674A33E7B66C48ABC4E796F71F3A1C55B7FB21BA7D367D2E
BC9104A9CCCB6A9449CE3EBCE19F5465C52D58CA35A8DDBF15A13D98CCB6300D
C61D6BC1CB3D8D43D2E1EFD5BA087DB36C3B527943A51112DC8F645FF716A64D
40ECD5212C7CCF827778A1E9AD87CBEEC58CA13285BE67784E635CB6F00B3CC0
4597E840D61A67702745A32BCEFC1472A9E47F835105FCC00D6F166A8185BC81
281DC86B84ACB6CDDB174046ADF53A3BDDFF18CC1CD9D0DA9C4A067719E7BB11
93ED2E17D8CE09DDE72C82BBEFC17A008619BAFDEF8A58DEBB4AF12CF66BEE80
B7857245CDD912CE12A6BCBE6F0B2970371B72015DAEA81FD19B213464550E83
7914A2768E6DE0DD34393E6AE13BDD02DB8A8B29DE9928DDEF7FFF24D7996A1B
DFF6949DA66BA266C20B1222D2C99E7D0A488B29FACECB2EEFE98E37F08DEDD9
6523333BEBD957555627ABE83C8477316E5B60BC03A0855EA13C0275906EE8C6
350E2C5F9040359242A48A1657BF928DA4EACD8392943482C54DADD1C9769C26
1FD76524234520CFF96C20BCC42B7B7B565EC15ACE461B36A794B765EA4B369B
2B25D834753EE915FC12BD88753E12915C78AF55BAC4B8ACBE7A9DA0B8ACB863
92F9BE8C563BC23419A8B7E810DE66294A31A1283867BA00C35DB13AA47589D7
F91F56911FFD07FCE49ADD1AE8D7F334F9D026631CB3C7B6499B8E230322C224
D63CF430F6B58F5C8F85ADC266065F1BD591E2CA66B1B7DDD1A3B8160590DF82
ECAD756D4EE4D60C4254785A021FB37C51F81B23393B5D96F384E5A07956DB67
FDC07CC2A051D50C881F9342699631D915AEC2A2A7765B43462A527204AFFABC
20F691515747F508A2F124FCE375FB412C5D8FE50A458872206BC7D39B0694A9
184861281667504F3556A121D9FD28DC2BF1AFC7090FDF6F27795607A67CC3BE
7372DCDD66E3D08226BC04B661C36CA7492A877AD3358FDD057E2CB1796E2744
D6E7C6D465D81BF02D8EE2107E7E3948EDB4FB3CD4326F8AC946187A41A982E3
F2F7912856C7F5D7171CEBCA0A2BFD2538FED70B0E7346FB33CA0D1CA75B901B
516B1EC9A6B5958BBB7932633B3FC3DFA4944A6399CFF0566E1F5F193AA150A5
632B9550CF7828197605DF40DF88E227D33D353DB7373CE442FAF642DA572F24
44EF63D815ECB6D68CF079D9203CB36F830FFEBB747D659A0C31C51A5D6F77F9
E036991BFD6E957299006E20A7FD55BC54180AB33D4EB3D7E7AFBBFBA6C30FA4
0ABB60E61EFCB557DEDAA9BA6AAA249F6C4198680F89AC0DBAD7E51C116B04E4
016D15C323B9FA56A0B82EE0A1B9CCEF696EF7496DCC2A6CD82832BD4598D19F
BF342F4DF373E1AF8B64FC9C2D3851E6B87F83BDA67FDA203CD787540D553239
63E33EC24AB07EC87C019F3758E4C7A686DF67C52F950CA76157176FBB58235C
4D3A1EAF644E5728DB56C66A7B41AB8C6C59627BAB3184D4EF7FB003B75FEB52
03B856AC222674B4C1389BC957D5E4A51DD8905B60A0B6776D4D527870E16DEA
DB20201ECC5A201E1A6DB4DB40BBE2F44ED7720B7B9E6B23FACF67AC252A42B7
091FD06DA0FE8F676CCFE900910FE08B57FB7D97F0A2664FD5B658371637D416
0EE0798825110D2308A5F34E354293250249461473DFF1572E811B0C938E8FC5
1B40D27B1E5670A02A7F3BBC19ECDF2091A9DB6A892C49E310A6E5F47EA43190
ABB6316A08346ECCD6B8C5ED992AA02061BC3CE3725F0C1E66A38841DE0D0020
CC1A4CA146FD7A74BC2F43B8E220D066DDEEDA11E3C2F46386191A51B01127EA
F1850E187E3A5238A62059637E2EB863FA4C0008DB011AD3A728D399FF1247BD
3050A2226CDB909C80F9F584249409CFA746AB0C850656BE8D56B7D66CB542B5
61AB71C6F4B75A11B0766DE5AB0A9295CBDCE2E65A418515D54FDA4B0DADC6E0
5BA1DC6DD9DEF54AF02D7ABC82FBF084AF1652AEFD26937C312DF982089C2C93
F9AA6E7166AB8D2AA7DC9CB71139CDC17F165B0495F225C2F42635EF48F0B8FE
0ED371993090220CBA38B4E6C36C143918F21AC2078B194C12B665CA7B3B7C2A
FB01D7CDB00E12E816B705D7FD325515D74DCCC46B0DD3CA00A98E0DD190CF3A
C22181575B83A18FE4FD4790BCF58EB955FA362D20E6A0DAEC2D66CE629B19F9
D712385BEDBFCD6415A406FDE70B26452AA4D7DD874BB0B05B9F95DC997692CC
5A1FD255DCE23401B3CC9075DF755F5FB57FE8FE127790161EA3F88F362EF14A
426206EB27174B339768367D80D86C5CB67AE4089646112342D6802F276EA9F1
7F6FF3494A7FD011155FE3135899F87F7D7454451F4F3BF5AB187AD4158B9076
F44624892F7A23D9CC50C0D3550FFEE6CA388EFE0BFFC4E68C6184F3DEE6A094
64E44DD1D362CBC4EB32EABC88847ED99D630CDF672C7E27A13499119A51BF46
A78584379C0999B795B7574DBC18F412AA38472AABAD7A5F0BA3ADE9A67DA0C8
6B3B68D093E92EE1F97BE1701EEBD8A1FFB6C3A4771B519A0528B0F7B79DFFFB
E38E9173BFA6536AACEB9084AD573B92C6B343AAF61BF39B79CDD4E05E240B2A
0C3381FE2E1FE161E76755C1567A222D06C98C8257A89EF076C4EE381D954C67
DC0F76D92E04AF2866C920555DA6D94EBB83FBF1D945A966E2D3ABF22CB2AB67
F3025204B0949D8D019BAE5131C008731D8CC58E286D6D4500A3445F07B0D85F
AB05ACE78B75003720054C3F6B612CF7976A3A4F1FEB0A323D4663174E96B65F
A56E81D887222450C07BD1432881FB4ED7DDFF6C7266EC816C3C9C8A368E88C1
AB96081C2DF39A07A2E193FAE01E988193BF7466C67F6298144C3C1AA74FDC30
ABA4473B90A7CD2CC38875B44240081886E8AC726087AD9225229D9D7C55F162
7D6C68535054D17ECCFC5530ABB8D170972BFC830FAD326FE1DCE06417AEFFB5
6622B92CB7286B7B5275C478519F6DF7CD8E9485213DD847C9B5D3EFAA3FFB17
D95225C63989CB5FFE7FD2DB50A8E1633FA07A43F27F71CE11FC795574F3849C
C52E145ADE7AE5B055E58A0FB82503BA2A72F7065DBD01996CAF8B688C73FFAD
1B58444464EBB9027F344E6E78AEDE284BC7C30AA224199947F64152D124521D
2AC2FC6E23436757E92C29DD3E8BB70617E635D1FC199DC43B7261E2D882AD1F
683FECB027EC0F4B36FF92B78245EF7B5D76B6E564D3EE36E163A383F7ABF03E
A65D1BB2CD098B1CEE3FFC6131EF29E29F0C6EB81D98B2CDEA8E08160A546A32
AB773F0212DE20D911505BF8CBAFF08BB3D73D3EB1A4785A71769B4C1663F6E5
5113D3BBE8A1A64807D1AD698DC43298C1EE91A06A35D61166482639C4146245
5DDA115E56095BB186155D9E35EBB5359BA39A458322D62C82680FFF70DB20AD
BBA08444E3727DFAF6ABFFB91A32DDEE63CE4CEAB3BE10890C52182651C47701
34AA57292B2DC4BAB108CAF242C6DDE04C7899552E2EAA46B6D3B72599971611
F6955D09CC651CAC37541C6A99487212B6EF696ACB09C832CEE0179AB498BEE7
C8A21F15366632156F5C4C68EA74AE68BDF1DCAE14767622E93A89C066E46321
87443240D217A79B55411514C229485F8205C1D4904D6F3A6E41F12558E3D0C0
D4AE714898060AD2D6B0AF207C89145431339C8274C99448EEC208FCCCC8EE5A
2190F12558505D04EF1494D7D8070AD2FD53BE1AE94BB046E37AE80EBBDFB4AD
E1A966DC1F9E71E7F256F04378706517D2CA2494FAE22794BCEA8B686C16945F
021446B83C7FE2AF11BE846B54E59C1AE14B888B7CA3457DC197A125EE3D0488
77E6F4C88BDECD3C196620CA0E97877092A3843BEAC71F7D07563C52E36879D4
4446980B1E70CB1C26F3611B4FD0230D7A74992E0726A77F4241A933BBD7102D
2A80912D55BE7AD2448460439B888BE051E08A74902A088E091F518F9E3E7DFC
D513FEFF3E0BE61184F7ECDEBA52C5AA50900B9FE14F00822B5E22F9D6101B91
A1D6A0365B0884590547754FDE5F7CB43CE0D37176C37B8A4CEC1FE5AF016D35
487249425D8F213634FDE5B45FE19FBE5A4E44C8660B9B3F2182B6500B238852
AA97CFB39B8CE3F1940FB74932C7E76C3A4405077C3BC00AFE1CEA8B90434BC2
CF5DDB268284695162D017F91886E1E1DF8FE03F63B830C5EC56E6DD0CE50E33
7FF4E3766B6B3828DF03A1A6DB6B77081A4CC110BD41838DD8D6F8790F9AB98F
EA7D49D1348B72354ED1CE0662C4D0A9D9304BC0B0F638A490EF9C52065AE240
D41D84553C624959CEB36BB8EFED11AE0F08C1425C2D5A0975B5F3421EE7B0A2
2F1776D4C8D0A282A3714C66277C0DCD13D52032462D89B04DB3DC88938999F8
E84853B39DF8DB68E8B1994F85074F448078D1ECC7DE9B5331278515D75ABDEA
3EC39827AE4B67B16BE48DEBD12D6A884868F44C77FE86927F21A4977BD42271
65AC89A1B9A0A92C525C9ECD042FB09EB8A5020122F97E7F33587555A703D955
DAE189626714095C1AABF5025F21A14F790BD192CC251E1F1F9C5569AC6DA5DD
90FCA67CBA8765577077EEBDDF13F3D10A09788C7859440EFDC4CAC56C0C8BFA
BDA095C24B2BBC7849018B5A46A826AEEAC8CAE3861442082E418966F57F712F
A9FDC4FFDB954B59A3FD2AFAED2746411BD7A080D27EF156455E9880AE83114E
54063ABB38BB7C22130BFF578AAEBDF0C6A774A2A4239B0487C03C13741644EC
C81991CFB4112B966F1C09EB94EFD3DA6DEEA16BC39083E23F7771F0F47D1C39
CAC64802CCB156BF88C1FAB938931063BD719E0C9BE4A706484E363E6C87A985
6127C24492A2E3AC6CC967C558DBE02BBF362A933F34A9B09F115C71B736EBED
E1EFFD90E3804B02BB5A24081DF06B280B8B46A36F2B83AFD998B3567742015D
5FAC0893D49FB16D4ABB763A7D9D16E2F2CA685E7D73AA78182BCDAA70B3BF47
C8EF836B2C10BD70954D6AFD6EC545D4FD1B4DEBB570A3B19AF51B2D9681C146
23D8F51B0D284504F8069C1024CC16B8C16D846AC8FABD1744740B3DE847D41D
83887ADC8F774B5DA0074E7CDCFCC11DA0593DCEE95B247F632DF18F50120DC3
8C97205A49F4835A4716883D1A4D0AB12BDB5C004A83EE3A6228169B0996771D
EB0D5F0CA4D3E84D66C2CE439124D416A8BDEC34EE488C5D8BE8B12E599F1E58
996209331977232A4FA6637C57A87B29B85681260CEBD2167A198DE2E0ACDAD7
5BB6D7AEC05D9A88D6683A570818722BADA51766A0FBE8F06898CF6077C9BF78
2E0DB8CBA64338A3E31CE0B309472628610EDA66DA793347D3E0755A30BA168D
F32BA7E6D4B857651D7889BAEDE0FEB8D961FF772ED2A29C670315AF08BE6A42
4E922599C8CCCB16E028880670CE9B055D00E74304DD2DAE9D4CD2619694E978
55DB66153B5C41B7050859DBE8DE61CB9487264DC453381509B35ED57E677CED
B027CB2D38C04902C9D07E925E4409A475DFE275856A277C476A4548C9B3F41D
7D9A58FC3E5C1EB0FFD020DB4BF6904F298747216CC11FBA85C790C99485C7F5
EE91AAF0F0E0DD3D92473ACBBD1DD8B8D83C697872BBA2480739C403E772EA79
456C3585A74F4F2E3B951ADA4B9F74956DC157C48B66FB3CDD6E9530223798AC
60BD0958CF5CD632C34899014FEE951EB23D4157986090279F1B04D8DF00DE91
60F050CBDAABFDCA54F2D01CE6D5E3A4FF5423B65D55B68DC106B1203ABE8821
1DB64A0BB754252E8659C7FD53AB86A87513696B1322FAC0F8C9EABB76C7A4A2
43C1695E43C02A4FEAAA98B9A3571D01F17838460D0139ABC31375EA29B6EFC0
E52FBCFDA3672F376C0B53AE6D3A3252A78A8D37A64F9F3970CD3026EB403562
8CF43D1B2CA6B03266210DC91E706106F2A88EA59836C385D64EC9763539E792
866DC0847657AF6492156C060F6620F82BFB54F83C09A560DD64B799BC2C69B3
BF5979AF280922A9C057674E99BCA1152A0B9D502C5C940A89113B451709C5D5
71AB33A03C14E1C04C02EB284A5283681FCA2F6D76E40A6882C727097680CF9E
0C91C5B0A929F42BC3548F4F63C4FAEE141B593D21F8E49FA6DACA7FAFC7ECA6
4621478BAFE94BA3E94BBB5B44BD2D0F13DB048903B65BD132DB18A06BBF4299
954599558532EB4A2CB3BF6D38266EBED3539F031B0392CB2BF0CE02EECED59E
6945B5E6899BDD9DDAAD498897A98BA8D64C070B6A028E12618F19A93E1B63CE
582FF2596D96CF16B3B6200A5E77888B1DB1D65D2BB937FCA8AAE46D427C15DD
13F10BE4B15CDB76D2C8CEE44B644F29577044AE3D498AD20A6D8E8BC86BB8C6
134F47603CDB4A7050FBC09BF92974620E00DA39A55BBFE39B48A7049CD70D0E
AEA2A38CA7289D3C810398A34DC8E524E4929B74ABD72CED5C828B94B830B5C4
9B3DC78BB4607BE9321D804B181BE4C354DEA5BA5B488F64BC9CCDE104E7DAE1
3E732F1AE60A1799A4F08867462629B86ED007116BD7AE5D27F053A03674B1B2
0FD57AA25E4172A277F47B0177D68E3C3C4BB645BAAC065A277C41A0991DF8C7
292DA6A274322B57CF290C72E0D25C48C39C5D83575DC7A435DB05C6F1E6C7C1
EB8FD91B084C8CBE938BEB36D8AA343658DF5A7670AF7FE5E1D7DF3C3EFCE6E0
F1E1DF0FAB1195D5360FF9C94E93091F4B3B57C96D3E497698B0211D4A0F70F2
8395F210C36290DD25EC0F6B9C9D330F5219A750552CEAEAF004060571E21000
A52BA51E1787F2FC10753F522F99DF14FAC9EFA3DE122E750041488886102A87
ABA770B19A8B25ED1A5070644FA8996D0B1F41C55075F2C0F1A05CD2ED8DA855
CB200DEEF0924790DDFCE2627B9FCA8DAE24E4D5097DD2D7FE8AFC97F05774CA
61B00BDA46923302DEEA5DE840DFD28A4BB116F063283A919EA582A353380402
4A7E18C10914894BB871F9C231127E3C7BF84E9E9FC7EFA21FFA61E34BF043C4
594665513B6F1A0972B8896FA242125D82F79CAB28ED420AC0D13DF2076EA444
504916267535093540C590879F7541D8AB8976C5744F372A84D78F33DC15246A
C5B785CD244F19C8FADADE1350BE241DF2EBDCF11B2B6CDA6FFF89F2DB4711AA
1CF61B818AFBC9C9E44A0CDE781C8D4EBC82CD2A31195111976672397DE1BC08
F7C5BBF1E0AB69838106297C0F86998CF156CEB3E94D6CABDB4C883F283EB19D
6E33C56DB5B8F98EB3DF1A5BEF0E3E345D0AEA8B56A1CC6A0E82AB2A1D669E93
683E9C94AA639E076E52D093E2D48AF34BF0E33A11C782CA5CCBB76B6326EB22
85C08D0926C0B6DC111C32E0276144C6D28651392A44E5048DE050F2A8A73649
96BA907F0622EA516EC1546185FB26DC2F16E5CC1A8C30A7724FD3C87B9F0E73
B369425B3B8074699A26B533E7D990268B7199C9D857F0EC212D7830CBB522AE
A9AFC71F425DC03B6F96E151B40FEC30D24D38ADD00847C768526979A986446A
12016C031343DCB4005EE8D2DB056C073EE3026AEBBEF53FAD510E0E964787D5
7BF0F4EAC67714F0ABD869BF3E0BAD85CCF38336B50CEF2BE7F81B1C28350381
C88551706D66F8B619AA829AE8D5FC7F74B47C5A59D5E9A86B95806B32C9B867
06EB71D59ACD72AE56E34237C8832440A6640292AEDB38053D2734E9554C90E8
6C1D6269A31CC1FDC73FC10E968C31FE859F6F0392D746907AFC1E5842791755
FDB639BEEE38033382116E2A604DB043D6AA9EA25078B1BE2AF810E2D30B640E
37120D3C8D028C8A6B8E1001DD56D65B4CC710A0AC057352BF7A41395E6F1E97
1503AE04F011418312AC77F882EAA1906562E9175F0EC9DAC2734E1840EEBF98
D94C14D9B925884028D6A35CAF568A88D1B79C844446BC2D8CF52A2F3A54650D
2921E9827C79D5D9127521D5531852E47C58B88AB8533324C98332D21D445D6E
C0BA9084E89CA66DF2388DA38F16DB5A98B020C2CBBCD261469C6F50940CB552
4036A9F4DE8A68035152D14D70A65DD3DE6658DAEE6368A3597CAB86366578C2
6A7C8627F810303C25D3C16D2E8309852684901989E20F218488BD498C725A39
3E67142F84168582B3E9155F124C9810A4683EDAD9091DC0AA2A2E14AA62958E
C77C983E6418D302C35A043097E1B4508AB761F5C776AFCBD880AA865A3B7C82
BE7DAC073EAB078FF9FF409D1DB7F4CF593E5EDDE453F674F935579BF89F6F96
87315F76A1277DBD3CFC3A2CABAD2580B1FDCB5AECE860D972AFD753D069588A
6D675A449B0B58010BF82508433AADC7C58E2F44AA838DF457960218EE606C89
0D4381A3E1FCC0F64C6710FA00FE457B075CABB4E8B0CF0971E889FF22536399
125D064012EB8107FDFF0F1217899678240100
}

; Below is an example of how to format a menu block. 
; Note that there are two menus in the following 
; block.  The "file" menu is indented and spread 
; across several lines.  The edit menu is all on
; one line.  Notice that you can place action blocks
; after each menu item, to be performed whenever the
; menu item is selected - as with the [print "You
; chose Item 1"] block below:

menu-data: [
    file: item "File" 
        menu [
            new:    item "Item 1" [print "You chose Item 1"]
            open:   item "Item 2" ; icons [1.png 2.png]
            ---
            recent: item "Look In Here..."
                menu [
                    item "WIN A PRIZE!" 
                    item "Try door number two"
                ]
            ---
            exit:   item <Ctrl-Q> "Exit"   
          ]
    edit: item "Edit" menu [item "copy" item "paste"]
] 

; Pay particular attention to the action block in the menu 
; style definition below.  You'll change it to execute your
; own selected functions for each possible menu choice.
; Most of the style definition is totally optional.  It's 
; designed to look like a native Microsoft menu.  The
; example at
; http://www.rebol.org/library/scripts/menu-system-demo.r
; contains many more examples of menu styles and options.
; The only part that's required in the example below is 
; the action block in the "item style" section.  Everything
; else serves only to adjust the cosmetic appearance of the
; menu:

winxp-menu: layout-menu/style copy menu-data xp-style: [
    menu style edge [size: 1x1 color: 178.180.191 effect: none]
        color white
        spacing 2x2 
        effect none
    item style 
        font [name: "Tahoma" size: 11 colors: reduce [
            black black silver silver]]
        colors [none 187.183.199] 
        effects none
        edge [size: 1x1 colors: reduce [none 178.180.191] 
            effects: []]    
        action [

            ; Change the lines below to fit your needs.
            ; You can use the action block of each item
            ; in the switch structure to run your own 
            ; functions.  "item/body/text" refers to the
            ; selected menu item.  This does the exact same
            ; thing as including a code block for each item
            ; in the menu definition above (i.e., you can
            ; put the [quit] block after the "exit" item 
            ; above, and it will perform the same way - 
            ; just like the "[print "You chose Item 1"]" 
            ; block after the "new" item above).

            switch/default item/body/text [
                "exit" [quit]
                "WIN A PRIZE!" [alert "You win!"]
                "Try door number two" [alert "Bad choice :("]
            ] [print item/body/text]  ; default thing to do
        ]
]

; Here is a simple function to trap the GUI close event.  This
; must be included whenever the menu module is used, or a 
; portion of the application will continue to run after being
; shut down. 

evt-close: func [face event] [
    either event/type = 'close [quit] [event]
]
insert-event-func :evt-close

; And finally, here's the user interface:

window: layout/size [

    ; The line below shows the winxp style menu:

    at 2x2 app-menu: menu-bar menu menu-data menu-style xp-style

    ; The line below shows the same menu, whenever the button 
    ; is clicked:

    at 150x200 btn "Menu Button" [
        show-menu/offset window winxp-menu
        0x1 * face/size + face/offset - 1x0
    ]
] 400x500

view center-face window

23. More About GUIs and Graphics

23.1 Responding to Special Events in a GUI - "feel"

Rebol's simple GUI syntax makes it easy for widgets to respond to mouse clicks. As you've seen, you can simply put the block of code you want evaluated immediately after the widget that activates it:

view layout [btn "Click me" [alert "Thank you for the click :)"]]

But what if you want your GUI to respond to events other than a mouse click directly on a widget? What if, for example, you want the program to react whenever a user clicks anywhere on the GUI screen (in a paint program, for example), or if you want a widget to do something after a certain amount of time has passed, or if you want to capture clicks on the GUI close button so that the user can't accidentally shut down an important data screen. That's what the "feel" object and "insert-event-func" function are used for.

Here's an example of the basic feel syntax:

view layout [
    text "Click, right-click, and drag the mouse over this text." feel [
        engage: func [face action event] [
            print action
            print event/offset
        ]
    ]
]

The above code is often shortened using "f a e" to represent "face action event":

view layout [
    text "Mouse me." feel [
        engage: func [f a e] [
            print a
            print e/offset
        ]
    ]
]

You can respond to specific events as follows:

view layout [
    text "Mouse me." feel [
        engage: func [f a e] [
            if a = 'up [print "You just released the mouse."]
        ]
    ]
]

You can also assign timer events to any widget, as follows:

view layout [
    text "This text has a timer event attached." rate 00:00:00.5 feel [
        engage: func [f a e] [
            if a = 'time [print "1/2 second has passed."]
        ]
    ]
]

Here's a button with a time event attached (a rate of "0" means don't wait at all). Every 0 seconds, when the timer event is detected, the offset (position) of the button is updated. This creates animation:

view layout/size [
    mover: btn rate 0 feel [
        engage: func [f a e] [
            if a = 'time [
                mover/offset: mover/offset + 5x5
                show mover
            ]
        ]
    ]
] 400x400

By updating the offset of a widget every time it's clicked, you can enable drag-and-drop operations:

view layout/size [
    text "Click and drag this text" feel [
        ; remember f="face", a="action", e="event":
        engage: func [f a e] [
            ; first, record the coordinate at which the mouse is
            ; initially clicked:
            if a = 'down [initial-position: e/offset]
            ; if the mouse is moved while holding down the button, 
            ; move the position of the clicked widget the same amount
            ; (the difference between the intial clicked coordinate
            ; recorded above, and the new current coordinate determined
            ; whenever a mouse move event occurs):
            if find [over away] a [
                f/offset: f/offset + (e/offset - initial-position)
            ]
            show f
        ]
    ]
] 600X440

Feel objects and event functions can be included right inside a style definition. The definition below allows you to easily create multiple GUI widgets that can be dragged around the screen. "movestyle" is defined as a block of code that's later passed to a widget's "feel" object, and is therefore included in the overall style definition (the remove and append functions have been added here to place the moved widget on top of other widgets in the GUI (i.e., to bring the dragged widget to the visual foreground)). You can add this "feel movestyle" code to any GUI widget to make it drag-able:

movestyle: [
    engage: func [f a e] [
        if a = 'down [
            initial-position: e/offset
            remove find f/parent-face/pane f
            append f/parent-face/pane f
        ]
        if find [over away] a [
            f/offset: f/offset + (e/offset - initial-position)
        ]
        show f
    ]
]

view layout/size [
    style moveable-object box 20x20 feel movestyle
    ; "random 255.255.255" represents a different random
    ;  color for each piece:
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    at random 600x400 moveable-object (random 255.255.255)
    text "This text and all the boxes are movable" feel movestyle
] 600x440

To handle global events in a GUI such as resizing and closing, "insert-event-func" is useful. The following example checks for resize events:

insert-event-func [
    either event/type = 'resize [
        alert "I've been resized"
        none   ; return this value when you don't want to
               ; do anything else with the event.
    ][
        event  ; return this value if the specified event
               ; is not found
    ]
]

view/options layout [text "Resize this window."] [resize]

You can use that technique to adjust the window layout, and specifically reposition widgets when a screen is resized:

insert-event-func [
    either event/type = 'resize [
        stay-here/offset:
            stay-here/parent-face/size - stay-here/size - 20x20
        show stay-here
        none   ; return this value when you don't want to
               ; do anything else with the event.
    ][
        event  ; return this value if the specified event
               ; is not found
    ]
]

view/options layout [
    stay-here: text "Resize this window."
] [resize]

To remove an installed event handler, use "remove-event-func". The following example captures three consecutive close events, and then removes the event handler, allowing you to close the GUI on the 4th try:

count: 1
evtfunc: insert-event-func [
    either event/type = 'close [
        if count = 3 [remove-event-func :evtfunc]
        count: count + 1
        none
    ][
        event
    ]
]

view layout [text "Try to close this window 4 times."]

For more information about handling events see http://www.rebol.com/how-to/feel.html, http://www.codeconscious.com/rebol/view-notes.html, and http://www.rebol.com/docs/view-system.html.

23.2 2D Drawing, Graphics, and Animation

With Rebol's "view layout" ("VID") dialect you can easily build graphic user interfaces that include buttons, fields, text lists, images and other GUI widgets, but it's not meant to handle general purpose graphics or animation. For that purpose, REBOL includes a built-in "draw" dialect. Various drawing functions allow you to make lines, boxes, circles, arrows, and virtually any other shape. Fill patterns, color gradients, and effects of all sorts can be easily applied to drawings.

Implementing draw functions typically involves creating a 'view layout' GUI, with a box widget that's used as the viewing screen. "Effect" and "draw" functions are then added to the box definition, and a block is passed to the draw function which contains more functions that actually perform the drawing of various shapes and other graphic elements in the box. Each draw function takes an appropriate set of arguments for the type of shape created (coordinate values, size value, etc.). Here's a basic example of the draw format:

view layout [box 400x400 effect [draw [line 10x39 322x211]]]
;  "line" is a draw function

Here's the exact same example indented and broken apart onto several lines:

view layout [
    box 400x400 effect [
        draw [
            line 10x39 322x211
        ]
    ]
]

Any number of shape elements (functions) can be included in the draw block:

view layout [
    box 400x400 black effect [
        draw [
            line 0x400 400x50
            circle 250x250 100
            box 100x20 300x380
            curve 50x50 300x50 50x300 300x300
            spline closed 3 20x20 200x70 150x200
            polygon 20x20 200x70 150x200 50x300
        ]
    ]
]

Color can be added to graphics using the "pen" function. Shapes can be filled with color, with images, and with other graphic elements using the "fill-pen" function. The thickness of drawn lines is set with the "line-width" function:

view layout [
    box 400x400 black effect [
        draw [
            pen red
            line 0x400 400x50
            pen white
            box 100x20 300x380
            fill-pen green
            circle 250x250 100
            pen blue
            fill-pen orange
            line-width 5
            spline closed 3 20x20 200x70 150x200
            polygon 20x20 200x70 150x200 50x300
        ]
    ]
]

Gradients and other effects can be easily applied to the elements:

view layout [
    box 400x220 effect [
        draw [
            fill-pen 200.100.90
            polygon 20x40 200x20 380x40 200x80
            fill-pen 200.130.110
            polygon 20x40 200x80 200x200 20x100
            fill-pen 100.80.50
            polygon 200x80 380x40 380x100 200x200
        ]
        gradmul 180.180.210 60.60.90
    ]
]

Drawn shapes are automatically anti-aliased (lines are smoothed), but that default feature can be disabled:

view layout [
    box 400x400 black effect [
        draw [
            ;  with default smoothing:
            circle 150x150 100
            ;  without smoothing:
            anti-alias off
            circle 250x250 100
        ]
    ]
]

23.2.1 Animation

Animations can be created with draw by changing the coordinates of image elements. The fundamental process is as follows:

  1. Assign a word label to the box in which the drawing takes place (the word "scrn" is used in the following examples).
  2. Create a new draw block in which the characteristics of the graphic elements (position, size, etc.) are changed.
  3. Assign the new block to "{yourlabel}/effect/draw" (i.e., "scrn/label/draw: [changed draw block]" in this case).
  4. Display the changes with a "show {yourlabel}" function (i.e., "show scrn" in this case).

Here's a basic example that moves a circle to a new position when the button is pressed:

view layout [
    scrn: box 400x400 black effect [draw [circle 200x200 20]]
    btn "Move" [
        scrn/effect/draw: [circle 200x300 20]  ; replace the block above
        show scrn
    ]
]

Variables can be assigned to positions, sizes, and/or other characteristics of draw elements, and loops can be used to create smooth animations by adjusting those elements incrementally:

pos: 200x50
view layout [
    scrn: box 400x400 black effect [draw [circle pos 20]]
    btn "Move Smoothly" [
        loop 50 [
            ; increment the "y" value of the coordinate:
            pos/y: pos/y + 1
            scrn/effect/draw: copy [circle pos 20]
            show scrn
        ]
    ]
]

Animation coordinates (and other draw properties) can also be stored in blocks:

pos: 200x200
coords: [70x346 368x99 143x45 80x125 237x298 200x200]

view layout [
    scrn: box 400x400 black effect [draw [circle pos 20]]
    btn "Jump Around" [
        foreach coord coords [
            scrn/effect/draw: copy [circle coord 20]
            show scrn
            wait 1
        ]
    ]
]

Other data sources can also serve to control movement. In the next example, user data input moves the circle around the screen. Notice the use of the "feel" function to update the screen every 10th of a second ("rate 0:0:0.1"). Since feel is used to watch, wait for, and respond to window events, you'll likely need it in many situations where animation is used, such as in games:

pos: 200x200
view layout [
    scrn: box 400x400 black rate 0:0:0.1 feel [
        engage: func [face action event] [
            if action = 'time [
                scrn/effect/draw: copy []
                append scrn/effect/draw [circle pos 20]
                show scrn
            ]   
        ] 
    ] effect [ draw [] ]
    across
    btn "Up" [pos/y: pos/y - 10]
    btn "Down" [pos/y: pos/y + 10]
    btn "Right" [pos/x: pos/x + 10]
    btn "Left" [pos/x: pos/x - 10]
]

Here's a very simple paint program that also uses the feel function. Whenever a mouse-down action is detected, the coordinate of the mouse event ("event/offset") is added to the draw block (i.e., a new dot is added to the screen wherever the mouse is clicked), and then the block is shown:

view layout [
    scrn: box black 400x400 feel [
        engage: func [face action event] [
            if find [down over] action [
                append scrn/effect/draw event/offset
                show scrn
            ]
            if action = 'up [append scrn/effect/draw 'line]
        ]
    ] effect [draw [line]]
]

A useful feature of draw is the ability to easily scale and distort images simply by indicating 4 coordinate points. The image will be altered to fit into the space marked by those four points:

view layout [
    box 400x400 black effect [
        draw [
            image logo.gif 10x10 350x200 250x300 50x300
            ; "logo.gif" is built into the REBOL interpreter
        ]
    ]
]

Here's an example that incoporates the image scaling technique above with some animation. IMPORTANT: In the following example, the coordinate position calculations occur inside the draw block. Whenever such evaluations occur inside a draw block (i.e., when values are added or subtracted to a variable coordinate position, size, etc.), a "reduce" or "compose" function must be used to evaluate those values. Notice the tick mark (') next to the "image" function. Function words inside a reduced block need to be marked with that symbol to evaluate correctly:

pos: 300x300
view layout [
    scrn: box pos black effect [
        draw [image logo.gif 0x0 300x0 300x300 0x300
    ]]
    btn "Animate" [
        for point 1 140 1 [
            scrn/effect/draw: copy reduce [
                'image logo.gif 
                (pos - 300x300)
                (1x1 + (to-pair rejoin ["300x" point]))
                (pos - (to-pair rejoin ["1x" point]))
                (pos - 300x0)
            ]
            show scrn
        ]
        for point 1 300 1 [
            scrn/effect/draw: copy reduce [
                'image logo.gif 
                (1x1 + (to-pair rejoin ["1x" point]))
                (pos - 0x300)
                (pos - 0x0)
                (pos - (to-pair rejoin [point "x1"]))
            ]
            show scrn
        ]
        ; no "reduce" is required below, because no calculations
        ; occur in the draw block - they're just static coords: 
        scrn/effect/draw: copy [
            image logo.gif 0x0 300x0 300x300 0x300
        ]
        show scrn
    ]
]

Here's another example of a draw block which contains evaluated calculations, and therefore requires "reduce"d evaluation:

view layout [
    scrn: box 400x400 black effect [draw [line 0x0 400x400]]
    btn "Spin" [
        startpoint: 0x0
        endpoint: 400x400
        loop 400 [
            scrn/effect/draw: copy reduce [
                'line 
                startpoint: startpoint + 0x1
                endpoint: endpoint - 0x1
            ]
            show scrn
        ]
    ]
]

The useful little paint program at http://rebol.org/cgi-bin/cgiwrap/rebol/view-script.r?script=paintplus.r consists of only 238 lines of code. Take a look at it to see how efficient Rebol's draw code is:

url: http://rebol.org/cgi-bin/cgiwrap/rebol/download-a-script.r?
script: "script-name=paintplus.r"
do rejoin [url script]
paint none []

For more information about built-in shapes, functions, and capabilities of draw, see http://www.rebol.com/docs/draw-ref.html, http://www.rebol.com/docs/draw.html, http://translate.google.com/translate?hl=en&sl=fr&u=http://www.rebolfrance.info/org/articles/login11/login11.htm (translated by Google), http://www.rebolforces.com/zine/rzine-1-05.html, http://www.rebolforces.com/zine/rzine-1-06.html (updated code for these two tutorials is available at http://mail.rebol.net/maillist/msgs/39100.html). A nice, short tutorial demonstrating how to build multi-player, networked games with draw graphics is available at RebolFrance (translated by Google). Also be sure to see http://www.nwlink.com/~ecotope1/reb/easy-draw.r (a clickable rebsite version is available in the REBOL Desktop -> Docs -> Easy Draw).

23.3 Using Animated GIF Images

Another easy way to work with animations in REBOL is with the "anim" style in GUIs. Anim takes a series of still image frames, and plays them in order as an animation with a given rate. The basic format is:

view layout [
    speed: 10
    anim rate (speed) [%image1.gif %image2.gif etc...]
]

The following script will convert an animated .gif into a folder filled with individual frame images:

REBOL []

gif-anim: load to-file request-file
make-dir %./frames/
count: 1

for count 1 length? gif-anim 1 [
    save/png rejoin [
        %./frames/ "your_file_name-" count ".png"
    ] pick gif-anim count
]

This next script will convert a directory of images (such as above, or any other series of images) into an embeddable block of REBOL code. It looks for all the images named [%your_file_name-1.* your_file_name-2.* etc...]:

REBOL []

system/options/binary-base: 64  
file-list: read  %./frames/
anim-frames-block: copy []
foreach file file-list [
    ; Unique portion of file names for your image frames go here.
    ; Leave out this check if you instead want to convert all
    ; files in the directory:
    if find to-string file "your_file_name-" [ 
        print file
        uncompressed: read/binary file
        compressed: compress to-string uncompressed
        append anim-frames-block compressed
    ]
]

editor anim-frames-block

Here's some sample output:

anim-frames-block: [64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zCHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxqjufmZWdnWxS/OsPJcODoPLtKprUcW9TPLXJT
V7LdFZIZuMx/Ll/rrJCkC3NZ1ztd6SpVCG+L363EsXpCTvhmtovzVCWurr7R6jG7
rzZarKFpd8XTS77Z1/Xu7Qn+vunr6+/v725rqv6nm/Oj4Or2Ll7jvDUOa8+e6FX3
3uYjbPz0fN/RKjbeWcU+Z5do2qfN2lWaelnXfbveKwkz7ytLqu0qBK6Xed1cyfhG
TC58xeujhyuF422FXxQeOPybbR1nzbbP18+khtXvu/H95Ns7Gzdv5ZtfaVX64fjZ
crf/d6xPvV7XmJ7PZ1/x/ueXm/nXrOfVZKyZ+DL8nt85zhWzqu8LPosvPyYZEdW8
QrJjvjdj3TOFJuXQFVEVEl0iC9L49pVJJvZcnR7XLn/w+ux64XUpizrvbF0R1PFx
4QvB3s29OxLylB9tW9Cj9+vEol5NLk+5ia7vLB74GvxbETxZRklSqI+HyWNpR7ri
VbkJtreOp05nF1O/EeGW9C01/RqjmVrF3l7PZxnfPStv12qxsjBYAwBolvDW2AQA
AA==
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxqjufmZWdnW6hqBUwQfnxuvkPltxaJLSsuLOTt
ZWPdIPzSaal3vZUth6nWhZUsq7NsrUqzQ9f47K17qyWmdW1T2txFsreLdW/Pydu6
rXe2mHrsYuf3j86uLn95Z1/Qf6ZnWeUGD2e38V/3WVOh9viYkfzh3Fvmb1Iap+oq
P7OUKH64ocH2tsisGfkvTy7nXi6nG/n11dGZzLv3RQt8On3c19zY7e8stbyDCxtf
h0rLZBZuKjyYFrv6jsLdZ8xr99lGi3wueRLuGN6+zqSq7MW1700y/hHle4o/PhP8
5Xt+397f3z88Pj3ff/++v79/vGdnYbAGAJfEqNM/BAAA
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blri2cIVNC+GU2Hp6elcEX0tnsbLfPpNs++9mTE57fRcyepfJZxfFgUsdNWU
s51l8ihoma+8XatU6cOQVaHCca6zQh+GrYvlrWOVnvbgxrzUo/POzrz2JmpuLuu+
VuntT+9ML316T3VWuf79HXX/t/GuKTJIPBj5UW7bzB0fko75frwVGzP1ffIRa934
tpiQp88O9Zq3q84pL3qwq593uZ621dus61NCJ097K/714b7l3tf1bAv03jfNmv/v
264t3wu2Hn0r9973y6uiy2aql235hJeef35hovexONmK8jc3rzapXLeL03r+6cXl
1fHn9+39/f3D49Pz/ffv+/v7x+fX98/v3////1NWFgZrALxatNdHBAAA
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxKZMWCZWdnSuW+urOSId11nkP+rx6JLS8C2l0n
y6XO2PLyUovvXDtTCdNXV5pCl8YtnRn68tq6qOVNX6tKdW4uT+ud5sv9RTt6Xt79
Vz3a4Stu7Cq7+OitZ/i7i3tza5n4tCo+3JzWdniTz5oI1cfHNOVXt2pWqp87VaPv
LZf1413C3s7pdmKys0rSL88PZGbbe+vzva1rY3+/PV32+sCubRtnnd0rkJdwj/0h
0wyemh2p644UC7fl7H778NGh3vO6fKbGX1/f2Jx9/9ze3d/fPzjczSvvv2/Pz88v
Lq+Oj7dTYLAGANdbpyswBAAA
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxqjufmZWdnWxS/unNy8/Lz8x2auWR/BTVeXOwi
Khe7y2Sl47KAiVamXApZV5b4rnWSXbVVO3RB3OF/PN7X1G9usjnfdXdl2dpz2/IK
D339VZZ3fVfZ2kdnd5uqx++t+/9tqvaMlWfXh3IrT7sZ/jHxaHim0zWtSqOnM6a9
FDtbU26cfkDPvrlNc1dm6kVTb22Lv5alaYfm5C+qu3OrNPfa+tzj13Ijv+XemZzI
zv9n+oq7Kye6f9+js2Fz5IFZx4PK+MR+JSy/sTn7/rm9u7+/f3C4m/m7pACDNQAX
yZ/iJgQAAA==
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWendyiezhkdy8zHemsfm9O5LG6m7zHGqjWKRCMo7MY+h4
Z/IrYGXwMp65dq2rAl6FrGJbG3fUKuB12DrPvVqs2gFvwlelHZ/ku3qadvSilMP7
9kqW653fWvay6ezq67rxS6r/P1qjPWPDg4Nu/N+/rvyh9/iYt7zzNs0So6enpi2M
cuuRNLp3qJH/d6hNlEnY+eXS09l6w0qzLq+PPP7s98yy3N2Fp5+dvTtVN78lqf77
u5XTi3wfHpYVj5lTnX3xfsHkeDe98qrS11catc/PK7D+/u74fnNpHv19e35+fnF5
dfz5fXt/f//w+PR8//37/v5mYGJisAYARqapGj4EAAA=
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8loBjYyMaj5ToqJNHjqOV0zsq/l56RnnjPlcq9t6Zy8+Nx8w+okFq8vywK6XDvl
ZGdNeR7Uyb9oUY7X55dH2INX7trCZbr62oIYSa+vv65mRDRHs05rrRR7GLU09+K+
v5LmD++sKuW/d3R2+YO4fbUn//G+MV+bsKpF9JzvnSKDx/vbhJ3DTkbo3j5coB2v
F72z4MzWubrBbLJWL25fWuZv7/d6y4q0bdMNj6udub7mzYnGuVV+v6qK8k/sl/We
l7Nb/+Ojyv5ytX0yFq/2LnRdfW3P79ef515b73/9nFRGSVPJ00c2fXwSf9685y1d
7B9ft/fu53ei/f3/5xnVtie8f33//P79wEKATeNBA4tYxoNGDrUVD5p4zF48aBZw
00h0ZGRksAYAd264o18EAAA=
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5tUvVi5Yia1eG5edqbPtPjSnpWBy/0YDCvDvvwsXh7Q6TL5
kI1UYGbQMv65Wq2nAl6FrApd++vIrA8HmRc4smbxni59cH294d46Vu2tOQc3OzDO
cc2+ujZiZ9zjc6mvr+hFNGV+/rT31bUX9xuTTybFWllsTFzXI5uv6xO2yXe3m669
nrfIxrAzDaLqx9bc2Jx8aVZ90bWcWYZXr6xj39+W++NT4K1VuZ9LeqPfpM2cWHj8
ytmQHx/u79b9zSf3e9un5iOth/QkYnd9fHVy/fSydbWl5e8PBbYHLreJ+1Oyv1d1
cX5tVe2Li+94t/X7y9b9Wf5y4mx3u5919d/Orr1+s8jyovr9ZFYpjol1XGYvHjQL
uGk8bBEJy3jYKpG24mGbTNmLh+0KbRqPOoTYWBisAbfrxM90BAAA
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxqjufmZWdnWxRHhRwIfu46z6Hx1xSJLSsuLOTt
1XLdFfDy0mIfTqu5t4xfOayKWMt04NRVretrAvc3yWqVrTm/LnqlUuusba9Ct6aL
ctQ4mL+9syt3+jHWgO+Nd/fVPXxm88p8Q8y+Gl7/q5Il667sZjp7S0drqm7UHP/T
UrJ7LNc/2zFFOXudlNWyG9uzvs6yO1NgEj29V3RXH2/1tzfTthVv9lt52+zdvcXZ
zPZ/rb99OKfvLF+vu+d50Xaju3b3bSutnj+fsTx4/sra6pK3N9fed2Op/2uR/OZ5
+/pQf7GKiJ37tlb905I3LVw7s//St1W7NgW8f/l1+41qZr6O+MxvjuH3m3jMXjxo
FnDTeNgiEpbxsFUibUViGyMjgzUAhlm/D2kEAAA=
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJGcFnIAgdVr2kGybtEJDernZmpnfsqp9P48bn5tvr/ZKSuPApY4Koo
Fzvry8OgZb6Sdq1Sog9DZjJlh/l6mLz2ZeDfU3c3SuClwzQm+RWsC6bqOC7JOrwo
Vnv72uht1gfbeK0n6MWtKW/8pbrj2/uI7QU/F9Vmf14XMbfnolxpjWlR3GGbyXZb
a3ZufLY619b5H8+vnNRL8z7K6ciWbnG80B7Y3SZrrZF7bVN+ee6q6uKr9/ZFM8/X
qfnx7s6xYPGrs+7oPXrWzex83qes6svaa+v/n9OrtUp9fX9ve7j/ux8fP3x61rjY
vLZ6b+iNdzsPre/9l5a86itjv21cXGXk5p+Wx+fVM3K9CK15v7MtwZlL74RCAp+b
xsMWkbCMh60SaSsetsmUvXjYrtCm8ahDZVrGo06NPFEBBmsAOJHArHoEAAA=
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4a8uKBYvd+6Wd
i/54bFp8YjKf9yqTzk2ph6ZqxZ4S4dj87Mw00+J7IjM3Pz/Xa1v674jElecXJrom
yq3NKFbwWC4/PSiE68FB5llMay/1aJkuClobLhqyV2pa9vUp8SeZBLjL1t7czDM7
S9ViukrMlpCNYj2V5YlB03x/7/uzu3RpQqsjL5tdjYFhyIF8yfehWT82Rmz3VxXf
9rvi0+VJs8zdv8lsLYo/NK2b699pqS93r20wLu/lrTbNvbYt3/rcWmv9x5f2prb7
1VZbvHxwrPO1n94u8+IzB/XV+/VsTEpfXl5pn+9Xbf3l6b2J1cHP+6psKhc/43zk
d99Cs/qrXW17eW3Nl7Jfp1aff17zb2/Rjz8/v8uWMf1aGt/IobbiQROP2YsHzQJu
Gg9bRMIyHrZKpK142CZT9uJhu0KbxqMOlWk7Eh0YrAGyBMCKdgQAAA==
} 64#{
eJxz93SzsEwMZwhn+M4AAg1g3ACmGsCsBjDnPxj/B1P/waz/YM4oGAXDBij+ZAHT
OiAClCfYOf4zMHLIeGxYcLCZQ1gr5sSGhYfbBZS95nhsXHS0W8I4686JjYuP9ys4
d8l4blpycrJG8KqYk5uWnp5ukHxqjufmZb79XEWvrlROfnRuvn21F4tXSOOFNptu
JttVBisuzfURtJsrdfXBleWhnHFLZ5VqX18V18lnImW6JmwT/yamD1ofHG9tZbi0
TLV6ytrbOwqeHkrNCtePaiypntX7u+z9rTml7OIxWiZrbhy2kbbm45IsTDrevTDu
GM/PgptrkzWj360qefhi9nLH+b09VUa3Z62zPN+zNkLt7fVt+eK21tHf8w40Jv7S
Oxv148Pxg73y1898t4h4Pnvh9rh5c9S+XjZbH/5+757K7y/22bc716+Lzn168ln4
db/1917kfwvbOH+6/zzLD8ez7p/X9/u1/d+fiEq2+Joe3owHjRxqKx408Zi9eNAs
4KbxsEUkLONhq0SaqACDNQAYMLy/ZgQAAA==
}]

And here's an example of how to write the files in that block back to the hard drive and display them in a GUI:

; Write files:

count: 1
make-dir %./frames/
for count 1 length? anim-frames-block 1 [
    write/binary rejoin [
        %./frames/ "frame-" count ".gif"
    ] to-binary decompress pick anim-frames-block count
]

; Create file list, with frames in numerical order:

file-list: read %./frames/
animation-frames: copy []
for count 1 length? file-list 1 [
    append animation-frames rejoin [
        %./frames/ "frame-" count ".gif"
    ]
]

; Display that file list as an animation:

view layout [
    anim: anim rate 10 frames animation-frames
]

23.4 3D Graphics with r3D

The "r3D" modeling engine by Andrew Hoadley is built entirely from native REBOL 2D draw functions. It demonstrates the significantly powerful potential of draw. The examples below show some of what you can accomplish with r3D:

do http://www.rebol.net/demos/BF02D682713522AA/i-rebot.r
do http://www.rebol.net/demos/BF02D682713522AA/histogram.r
do http://www.rebol.net/demos/BF02D682713522AA/objective.r

The r3D engine is small. Here's the entire module in compressed, embeddable format (this is all just standard REBOL code compressed into a more compact format). To enable 3D graphics in your REBOL programs, just include this text in your code (paste it, or "do" it from a file). If you'd like to read and learn from the pure REBOL code that makes up this module, see the examples above (the r3D module is included in those examples as regular text code):

do to-string decompress 64#{
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
}

Here's a simple example that demonstrates the basic syntax and use of r3D. Be sure to do the code above before running this example:

Transx:  Transy:  Transz: 300.0         ; Set some camera
Lookatx:  Lookaty:  Lookatz: 100.0      ; positions to
                                        ; start with.
do update: does [           ; This "update" function is where
    world: copy []          ; everything is defined.
    append world reduce [   ; Add your 3D objects inside this "append".
        reduce [cube-model (r3d-scale 100.0 150.0 125.0) red]
    ]                       ; A red 'cube' 100x150x125 pixels is added.
    camera: r3d-position-object   
        reduce [Transx Transy Transz]
        reduce [Lookatx Lookaty Lookatz]
        [0.0 0.0 1.0] 
    RenderTriangles: render world camera r3d-perspective 250.0 400x360
    probe RenderTriangles   ; This line demonstrates what's going on
]                           ; under the hood.  You can eliminate it.

view layout [
    scrn: box 400x360 black effect [draw RenderTriangles]  ; basic draw
    across return
    slider 60x16 [Transx: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transy: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transz: (value * 600) update show scrn]
    slider 60x16 [Lookatx: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookaty: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookatz: (value * 200 ) update show scrn]
]

R3D works by rendering 3D images to native REBOL 2D draw functions, which are contained in the "RenderTriangles" block above. R3D provides basic shape structures and a simple language interface to create and view those images in a REBOL application. It automatically adjusts lighting and other characteristics of images as they're viewed from different perspectives. To see how the rendering of images is converted into simple REBOL draw functions, watch the output of the "probe RenderTriangles" line in the REBOL interpreter as you adjust the sliders above. It displays the list of draw commands used to create each image in the moving 3D world.

In the example above, slider widgets are used to adjust values in the animation. Those values could just as easily be controlled by loops or other forms of data input. In the example below, the values are adjusted by keystrokes assigned to empty text widgets (use the "asdfghqwerty" keys to move the cube):

Transx:  Transy:  Transz: 2.0
Lookatx:  Lookaty:  Lookatz: 1.0 
do update: does [
    world: copy []
    append world reduce [ 
        reduce [cube-model (r3d-scale 100.0 150.0 125.0) red]
    ]
    Rendered: render world 
        r3d-position-object   
        reduce [Transx Transy Transz]
        reduce [Lookatx Lookaty Lookatz]
        [0.0 0.0 1.0]
        r3d-perspective 360.0 400x360
]
view layout [
    across
    text "" #"a" [Transx: (Transx + 10) update show scrn]
    text "" #"s" [Transx: (Transx - 10) update show scrn]
    text "" #"d" [Transy: (Transy + 10) update show scrn]
    text "" #"f" [Transy: (Transy - 10) update show scrn]
    text "" #"g" [Transz: (Transz + 10) update show scrn]
    text "" #"h" [Transz: (Transz - 10) update show scrn]
    text "" #"q" [Lookatx: (Lookatx + 10) update show scrn]
    text "" #"w" [Lookatx: (Lookatx - 10) update show scrn]
    text "" #"e" [Lookaty: (Lookaty + 10) update show scrn]
    text "" #"r" [Lookaty: (Lookaty - 10) update show scrn]
    text "" #"t" [Lookatz: (Lookatz + 10) update show scrn]
    text "" #"y" [Lookatz: (Lookatz - 10) update show scrn]
    at 20x20
    scrn: box 400x360 black effect [draw Rendered] 
]

The r3D module can work with models saved in native .R3d format, and the "OFF" format (established by the GeomView program at http://www.geom.uiuc.edu/projects/visualization/. See http://local.wasp.uwa.edu.au/~pbourke/dataformats/oogl/#OFF for a description of the OFF file format). A number of OFF example objects are available at http://www.mpi-sb.mpg.de/~kettner/proj/obj3d/.

To understand how to create/import and manipulate more complex 3D shapes, examine the way objects are designed inside the "update" function in each of Andrew's three examples. Here's a simplified variation of Andrew's objective.r example that loads .off models from the hard drive. Be sure to do the r3D module code above before running this example, and then try downloading and loading some of the example .off files at the web site above:

RenderTriangles: []
view layout [
    scrn: box 400x360 black effect [draw RenderTriangles]
    across return
    slider 60x16 [Transx: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transy: (value * 600 - 300.0) update show scrn]
    slider 60x16 [Transz: (value * 600) update show scrn]
    slider 60x16 [Lookatx: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookaty: (value * 400 - 200.0) update show scrn]
    slider 60x16 [Lookatz: (value * 200 ) update show scrn]
    return btn "Load Model" [
        model: r3d-load-OFF load to-file request-file
        modelsize: 1.0
        if model/3 [modelsize: model/3]
        if modelsize < 1.0 [ modelsize: 1.0 ]
        defaultScale: 200.0 / modelsize
        objectScaleX: objectScaleY: objectScaleZ: defaultscale
        objectRotateX: objectRotateY: objectRotateZ: 0.0
        objectTranslateX: objectTranslateY: objectTranslateZ: 0.0   
        Transx:  Transy:  Transz: 300.0
        Lookatx:  Lookaty:  Lookatz: 200.0
        modelWorld: r3d-compose-m4 reduce [
            r3d-scale objectScaleX objectScaleY objectScaleZ
            r3d-translate 
                objectTranslateX objectTranslateY objectTranslateZ
            r3d-rotatex objectRotateX
            r3d-rotatey objectRotateY 
            r3d-rotatez objectRotateZ
        ]
        r3d-object: reduce [model modelWorld red]
        do update: does [
            world: copy []  
            append world reduce [r3d-object]
            camera: r3d-position-object   
                reduce [Transx Transy Transz]
                reduce [Lookatx Lookaty Lookatz]
                [0.0 0.0 1.0] 
            RenderTriangles: 
                render world camera r3d-perspective 250.0 400x360
        ]
        update show scrn
    ]
]

Like most REBOL solutions, r3D is a brilliantly simple, compact, and powerful design that doesn't require any external toolkits. It's pure REBOL, and it's really amazing!

24. Multitasking

"Threads" are a feature of modern operating systems that allow multiple pieces of code to run concurrently, without waiting for the others to complete. Without threads, individual portions of code must be evaluated in consecutive order. Unfortunately, REBOL does not implement a formal mechanism for threading at the OS level, but does contain built-in support for asynchronous network port and services activity. See http://www.rebol.net/docs/async-ports.html, http://www.rebol.net/docs/async-examples.html, http://www.rebol.net/rebservices/services-start.html, and http://www.rebol.net/rebservices/quick-start.html for more information.

The following technique provides an alternate way to evaluate other types of code in a multitasking manner:

  1. Assign a rate of 0 to a GUI item in a 'view layout' block.
  2. Assign a "feel" detection to that item, and put the actions you want performed simultaneously inside the block that gets evaluated every time a 'time event occurs.
  3. Stop and start the evaluation of concurrently active portions of code by assigning a rate of "none" or 0, respectively, to the associated GUI item.

The following is an example of a webcam viewer which creates a video stream by repeatedly downloading and displaying images from a given webcam url. To create a moving video effect, the process of downloading each image must run without stopping (i.e., in some sort of unending "forever" loop). But for a user to control the stop/start of the video flow (by clicking a button, for example), the interpreter must be able to check for user events that occur outside the forever loop. By running the repeated download using the technique outlined above, the program can continue to respond to other events while continuously looping the download code:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    btn "Start Video" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Video" [webcam/rate: none show webcam]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ]
]

Here's an example in which two webcam video updates are treated as separate processes. Both can be stopped and started as needed:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    across 
    btn "Start Camera 1" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Camera 1" [webcam/rate: none show webcam]
    btn "Start Camera 2" [
        webcam2/rate: 0 
        webcam2/image: load webcam-url 
        show webcam2
    ]
    btn "Stop Camera 2" [webcam2/rate: none show webcam2]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ]
    webcam2: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ] 
]

Unfortunately, this technique is not asynchronous. Each piece of event code is actually executed consecutively, in an alternating pattern, instead of simultaneously. Although the effect is similar (even indistinguishable) in many cases, the evaluation of code is not concurrent. For example, the following example adds a time display to the webcam viewer. You'll see that the clock is not updated every second. That's because the image download code and the clock code run alternately. The image download must be completed before the clock's 'time action can be evaluated. Try stopping the video to see the difference:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    btn "Start Video" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Video" [webcam/rate: none show webcam]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ] 
        ] 
    ]
    clock: field to-string now/time/precise rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/text: to-string now/time/precise show face
            ] 
        ] 
    ]
]

One solution to achieving truly asynchronous activity is to simply write the code for one process into a separate file and run it in a separate REBOL interpreter process using the "launch" function:

write %async.r {
    REBOL []
    view layout [
        clock: field to-string now/time/precise rate 0 feel [
            engage: func [face action event][
                if action = 'time [
                    face/text: to-string now/time/precise show face
                ] 
            ] 
        ]
    ]
}

launch %async.r
; REBOL will NOT wait for the evaluation of code in async.r
; to complete before going on:

webcam-url: http://209.165.153.2/axis-cgi/jpg/image.cgi
view layout [
    btn "Start Video" [
        webcam/rate: 0 
        webcam/image: load webcam-url 
        show webcam
    ]
    btn "Stop Video" [webcam/rate: none show webcam]
    return 
    webcam: image load webcam-url 320x240 rate 0 feel [
        engage: func [face action event][
            if action = 'time [
                face/image: load webcam-url show face
            ]
        ]
    ]
]

The technique above simply creates two totally separate REBOL programs from within a single code file. If such programs need to interact, share data, or respond to interactive activity states, they can communicate via tcp protocol, or by reading/writing data via a shared storage device.

25. DLLs and Shared Code Files

25.1 What They are and Why to Learn About Them

Some of the most important tools in modern programming exist in the form of "shared code" files. In Windows, shared code files are called "Dll"s ("dynamically linked libraries"). On Linux they're called "So" files, and on Mac they're called "Dylib" files. Shared code files are especially useful because they allow libraries of functions to be shared among different programming languages. They provide a common syntax interface between different development environments, allowing contained functions, parameters, variables, etc. to be accessed and used by any language capable of working with the shared code format.

A large majority of all executable code in the Windows operating system exists in the form of Dlls. The Windows "api" ("application programming interface") exposes thousands of existing functions that exist inside many scattered Dll files on your hard drive. You can access those functions to complete any programming task that isn't natively possible in the REBOL language. If you want to create intricate software for Linux, you'll need to learn about the functions built into that operating system's shared code files, and the same is true for Mac or any other OS. Controlling any operating system at a very fine level is accomplished by manipulating functions inside those files.

Besides your operating system api files, there are many thousands of third party DLLs written by programmers all over the world, which contain software components that allow you to do useful things as a developer. Pre-created shared code modules can help you easily perform everything from visual 3D programming, to VOIP communication, to database management, to robotic equipment handling, to ... well, anything else that can be done with a computer. Such modules are available both commercially and for free, and learning how to use them is a valuable skill. Take a look at http://www.freebasic.net/index.php/viewall?page=link&category=lib, http://perso.orange.fr/xblite/libraries.html, and http://www.thefreecountry.com/sourcecode/index.shtml for sample listings of some well known free Dlls which Windows developers use to build complex software. Also take a look at http://sourceforge.net for a large online repository of free developer tools. Many of the useful software components at those links come in the form of Dlls and shared code files for various operating systems.

REBOL keeps you from having to learn the many various complex apis of all the potential operating systems it runs on, and it allows you to accomplish many useful tasks without ever having to use third party tools. But there are some things that simply can't be done with raw REBOL code. For example, you can't directly control many types of hardware and you can't make use of certain lesser used operating system functions. There are also some things which aren't practical for average coders to write from scratch (3D game engines, complex data management systems, etc.). To do those things, you're best off using software programming tools that teams of other programmers have spent years laboring over. To benefit from such existing tools, you'll often need to access Dlls.

25.2 How To Use Them

To use Dlls and other shared code files in REBOL, you'll need to download one of the recent beta versions of REBOL from http://www.rebol.net/builds/ (older versions required a paid license for Dll access). We'll need to use either rebview.exe, rebface.exe, or rebcmdview.exe for the examples in this section.

To understand how Dlls and other shared code files work, download and save the following file: http://musiclessonz.com/pureexample.dll. This Dll was written in the Purebasic programming language (another great programming tool found at http://purebasic.com). Here's the code that was used to create it:

ProcedureDLL AttachProcess(Instance.l)
    Global Message.s = "This part of the program was written in
        Purebasic.  Next, the purebasic code will multiply the 
        values previously entered into the REBOL interpreter, 
        and return the result back to the REBOL script..."
EndProcedure

ProcedureDLL DoSomeStuff(x.l, y.l)
    MessageRequester("Purebasic", Message)
    ProcedureReturn x * y
EndProcedure

Run the following code in either "rebview.exe" or "rebcmdview", and you'll see output created by a combination of REBOL code and the Purebasic code above:

REBOL []
print "--- This part of the program was written in Rebol:"
print newline
a: to-integer ask "Enter a random number (integer):  "
b: to-integer ask "Enter another random number:  "

; This part of the code uses the pureeexample.dll.
; the DoSomeStuff function is loaded from the pureexample.dll
; and converted to the new "rebol-function":
lib: load/library %pureexample.dll
rebol-function: make routine! [
    x [integer!]
    y [integer!] 
    return: [integer!]
] lib "DoSomeStuff"
; the rebol-function is called, and the result is saved in the
; "answer" variable:
answer: rebol-function a b

print newline
print "--- This part of the program was written in Rebol:"
print newline
print rejoin [
    "The multiplication result calculated by the Purbasic Dll was:  "
    answer
]
print newline
free lib
halt

You do not have to understand the Purebasic language in order to use the Dll. Just as important, you don't need to own the Purebasic compiler, or have any other skills/tools related to Purebasic. All you need to know is that the Dll contains 1 function, and that that function accepts 2 passed integer parameters and returns an integer value. The format used in REBOL to access the function from the Dll is as follows:

lib: load/library %TheNameOfYour.DLL

; "TheFunctionNameInsideTheDll" is loaded from the Dll and converted
; into a new REBOL function called "your-rebol-function-name":
your-rebol-function-name: make routine! [
    return-value: [data-type!]
    first-parameter [data-type!] 
    another-parameter [data-type!] 
    more-parameters [and-their-data-types!]
    ...
] lib "TheFunctionNameInsideTheDll"

; When the new REBOL function is used, it actually runs the function
; inside the Dll:
your-rebol-function-name parameter1 parameter2 ...

free lib

The first line opens and loads the functions contained in the specified Dll. The following lines convert the function contained in the Dll to a format that can be used in REBOL - those lines are the heart of what you need to understand. To make the conversion, a REBOL function must be labeled and defined (i.e, "your-rebol-function-name" above), and a block containing the labels and types of parameters used and values returned from the function must be provided ("[return: [integer!]]" and "first-parameter [data-type!] another-parameter [data-type!] more-parameters [and-their-data-types!]" above). The name of the function, as it's labled in the Dll, must also be provided immediately after the parameter block ("TheFunctionNameInsideTheDll" above). The second to last line above actually executes the new REBOL function, using any appropriate parameters you choose (this can happen as many times as you want in your program, once the new function has been defined). When you're done using functions from the Dll, the last line is used to free up the Dll so that it's closed by the operating system.

Using the format above, you can access and use the functions contained in most DLLs, as if they're native REBOL functions. That allows you to make use of a whole world of ready-made software components to instantly accomplish complex programming goals. Finding and becoming familiar with functions in shared code modules that you consider useful will likely become an important part of your future learning activity.

Here's another example that requests some text at the REBOL command prompt, and then displays that text in a message box. The message box uses the Windows api 'MessageBoxA' function, which is contained in the 'User32.dll' file as a standard part of the Windows operating system. You don't need to download the Dll or move it anywhere in particular on your hard drive - it'll be found automatically by the OS when loaded in REBOL. Notice how the outline of this code mirrors the general example above:

REBOL []

text:  ask "Please enter some text:  "

lib: load/library %User32.dll

; The MessageBoxA function is loaded from User32.dll and
; converted into a new REBOL function called "message-box":
message-box: make routine! [
    a [integer!] b [string!] c [string!] d [integer!]
] lib "MessageBoxA"

; The new message-box function is used, which actually runs
; the code within the dll:
message-box 0 text "You typed:" 0
free lib
halt

The next example uses the Windows api 'Beep' function, contained in the 'kernel32.dll' file. It takes two integer parameters representing the pitch and duration of the beep:

REBOL []

lib: load/library %kernel32.dll

; The "Beep" function from the Dll is converted to a new Rebol
; function called "playsound":
play-sound: make routine! [
    return: [integer!] pitch [integer!] duration [integer!]
] lib "Beep"

for hertz 0 5000 10 [
    print rejoin ["The pitch is now " hertz " hertz."]
    ; The converted function is put to use:
    play-sound hertz 50
]

free lib
halt

For more information about standard functions contained in the Windows api, see http://www.allapi.net/downloads/apiguide/agsetup.exe and http://www.activevb.de/rubriken/apiviewer/index-apiviewereng.html. These help applications were created for the Visual Basic programming language, but the variety of examples should be fairly easy to recognize and convert to REBOL if you focus on the api function names, passed parameters and return value. For more complete coverage of many api functions, download the help files at ftp://ftp.borland.com/pub/delphi/techpubs/delphi2/win32.zip or http://www.borland.com/devsupport/borlandcpp/patches/BC52HLP1.ZIP.

The next example uses the "dictionary.dll" from http://www.reelmedia.org/pureproject/archive411/dll/Dictionary.zip to perform a spellcheck on text entered at the REBOL command line. There are two functions in the dll that are required to perform a spellcheck - "Dictionary_Load" and "Dictionary_Check":

REBOL []

check-me: ask "Enter a word to be spell-checked:  "

lib: load/library %Dictionary.dll

; Two new REBOL functions are created:

load-dic: make routine! [
    a [string!] 
    return: [none]
] lib "Dictionary_Load"

check-word: make routine! [
    a [string!]
    b [integer!]
    return: [integer!]
] lib "Dictionary_Check"

; This line runs the Dictionary_Load function from the DLL:

load-dic ""

; This line runs the Dictionary_Check function in the DLL:

response: check-word check-me 0

; The Dictionary_Check function returns 0 if there are no errors:

either response = 0 [
    print "No spelling errors found." 
] [
    print "That word is not in the dictionary."
]

free lib
halt

The next example plays an mp3 sound file of your choice using the Dll at http://musiclessonz.com/play_mp3.dll. Although you don't need to understand it, the Purebasic code used to create the Dll is shown below. This dll is useful because REBOL doesn't natively support any way to play mp3s or other common media files:

ProcedureDLL playfile (name.s)
    If InitMovie()
        LoadMovie(0, name) 
        PlayMovie(0, #Null)
    EndIf
    If OpenConsole()
        Print("Press any key to stop playback")
        While A$=Inkey()
        Wend
    EndIf
    CloseConsole()
EndProcedure

To make use of this example, the actual compiled Dll file (the one available for download at the link above) has been compressed and embedded directly into the REBOL code. This was done using the "REBOL Binary Embedder" presented earlier in this tutorial. The dll is decompressed and written to the hard drive directly from the code, so it does not need to be downloaded or included separately on your computer. This provides a way to play mp3 files in REBOL without any external file dependencies. Just copy and paste the code, then call the play-mp3 function. Because Purebasic is a cross platform compiler, the exact same shared library in this example could be recompiled and used to provide the same functionality on other operating systems (you need the Purebasic compiler to do that). By using shared code libraries in this way, Purebasic can be a useful tool for adding extended multimedia capabilities to Rebol:

REBOL []

; Here's the compressed dll:

dll-module: load to-binary decompress 64#{
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
YJbb5XKLbx+FWXWq6yu5kcaGRPbvUfYv1q4H5Q8s8K7fB499nbHS+IrxqPEdYxrz
VeZhJpcpZ9YzQeabTJjpYb7LPMnsZV5hBpiTzD8xi01WU7GpwtRg6jBtNm0z7TLt
Mf3A9JzpkClmet90zvRvpt+aFphtZp85aA6bt5n3mJ8xHzS/Zh40HzO/Zz5nHjFf
NSdZpliyLHMsRovd4rR8w1JnabK0WFhLt2W35UXLK5bXLacs71s+t0yy6qzTrH9q
NVgXWxmr3VpiXWGttW6w+q3ftH7b2mv9c+te6z7rfutL1kHrMetJ6z9bP7J+Yr1s
vW4luZpcXW5W7ozchblLch25Vbn1uR25z+UezT2Vq8qry9uR9/28fXkv5Q3mvZf3
Ud5I3kM2o22Drdu22/as7ce2Q7bXbEdtb9mGbL+16ZbOWLpg6cb8tvz9+Qfzf5kf
yz+Zfyb/fP7F/M/yP89X23X2bHuevdJeb99kD9t5e6/9HftZ++ICZ0FjwdaCPQUv
FrxRcKbgWsFiR4HjLx0HHG843nF85lhYWFG4prCpsLvw54W/LhwtvFmoK5pVlFNU
VuQueryot+jVomNFY0X4VMYHYp6x0Nhj3Gt8zvgT4wGI06+MZ4wfGj82fmq8ZpSM
WiaDmcUsYEyMjSlkGphmppXpYDYzXcx3mF7me8xTzDPMPuZHzAvMAeYw8/fMEeYf
mVPMGeYsc54RmU+Yy8x1hpgm4++wLsgFTAvjKuMXm5gPyv9G+U8ki8fjACgAAA==
}

; Now the Dll is written to the hard drive, the "playfile"
; function is loaded from the Dll, and converted to the
; new REBOL "play-mp3" function:

write/binary %play_mp3.dll dll-module

lib: load/library %play_mp3.dll
play-mp3: make routine! [a [string!] return: [none]] lib "playfile"

; Then an mp3 file name is requested from the user, which is played
; by the "playfile" function in the Dll:

file: to-local-file to-string request-file
play-mp3 file

print "Done playing, Press [Esc] to quit this program: "
free lib

Here's an example that uses the Dll version of the "AutoIt" programming language. AutoIt is a useful programming tool for the Windows operating system that allows you to interact with other running programs, in order to automate time consuming activities. The AutoIt compiler and the Dll version of the language ("autoitx") are available for free at http://www.autoitscript.com/autoit3/downloads.php. AutoIt contains a wide variety of functions to programatically push buttons, type text, select menu items, choose items from lists, control the mouse, etc. in any existing program window, as if those actions had been performed by a user clicking and typing on screen. Because those actions can be performed programatically, they can be executed without error, and at lightning speed. This allows you to automate repetitive tasks, and to customize the use of existing applications in ways that satisfy users' needs very specifically. The code below uses the "AU3_MouseMove" function from the AutoIt Dll to move the mouse around the screen. Learning the other functions in the AutoIt language can be very helpful in customizing and automating existing Windows applications:

REBOL []

lib: load/library %AutoItX3.dll
move-mouse: make routine! [
    return: [integer!] x [integer!] y [integer!] z [integer!]
] lib "AU3_MouseMove"

print "Press the [Enter] key to see your mouse move around the screen."
print "It will move to the top corner, and then down diagonally to"
ask "position 200x200:  " 

for position 0 200 5 [
    move-mouse position position 10 
    ; "10" refers to the speed of the mouse movement
]

free lib
print "^/Done.^/"
halt

Here is a final application that uses Dll functions from the native Windows API to view video from a local web cam, to save snapshots in BMP format, and to change the REBOL GUI window title:

REBOL []

; First, open the Dlls that contain the Windows api functions we want
; to use (to view webcam video, and to change window titles):

avicap32.dll: load/library %avicap32.dll
user32.dll: load/library %user32.dll

; Create REBOL function prototypes required to change window titles:
; (These functions are found in user32.dll, built in to Windows.)

get-focus: make routine! [return: [int]] user32.dll "GetFocus"
set-caption: make routine! [
    hwnd [int] a [string!]  return: [int]
] user32.dll "SetWindowTextA"

; Create REBOL function prototypes required to view the webcam:
; (also built in to Windows)

find-window-by-class: make routine! [
    ClassName [string!] WindowName [integer!] return: [integer!]
] user32.dll "FindWindowA"
sendmessage: make routine! [
    hWnd [integer!] val1 [integer!] val2 [integer!] val3 [integer!]
    return: [integer!]
] user32.dll "SendMessageA"
sendmessage-file: make routine! [
    hWnd [integer!] val1 [integer!] val2 [integer!] val3 [string!]
    return: [integer!]
] user32.dll  "SendMessageA"
cap: make routine! [
    cap [string!] child-val1 [integer!] val2 [integer!] val3 [integer!]
    width [integer!] height [integer!] handle [integer!] 
    val4 [integer!] return: [integer!]
] avicap32.dll "capCreateCaptureWindowA"

; Create the REBOL GUI window:

view/new center-face layout/tight [
    image 320x240
    across
    btn "Take Snapshot" [
        ; Run the dll functions that take a snapshot:
        sendmessage cap-result 1085 0 0
        sendmessage-file cap-result 1049 0 "scrshot.bmp"
    ]
    btn "Exit" [
        ; Run the dll functions that stop the video:
        sendmessage cap-result 1205 0 0
        sendmessage cap-result 1035 0 0
        free user32.dll
        quit
    ]
]

; Run the Dll functions that reset our REBOL GUI window title:
; (eliminates "REBOL - " in the title bar)

hwnd-set-title: get-focus
set-caption hwnd-set-title "Web Camera"

; Run the Dll functions that show the video:

hwnd: find-window-by-class "REBOLWind" 0
cap-result: cap "cap" 1342177280 0 0 320 240 hwnd 0
sendmessage cap-result 1034 0 0
sendmessage cap-result 1077 1 0
sendmessage cap-result 1075 1 0
sendmessage cap-result 1074 1 0
sendmessage cap-result 1076 1 0

; start the GUI:

do-events

A complete survey of Dlls and shared code modules in common use is beyond the scope of this tutorial, but the links above should provide a starting point. For more information about Dlls, see the following pages:

http://rebol.com/docs/library.html

http://en.wikipedia.org/wiki/Dynamic_Link_Library

http://msdn.microsoft.com/library/

http://www.math.grin.edu/~shirema1/docs/DLLsinREBOL.html

Exploring Dlls and other shared modules will undoubtedly lead you toward learning new programming languages and concepts. Once you've completed this tutorial, you'll be able to accomplish quite a bit of useful programming solely with the REBOL language, but your studies will likely continue down other paths for as long as you're interested in improving your skills. The AutoIt and Purebasic languages are useful tools that are easy to learn and complimentary to Rebol's strengths. They're worth checking out when you're curious about other languages.

Remember, whenever you use any Dll or code created by another programmer, be absolutely sure to check, and follow, the licensing terms by which it's distributed.

26. Understanding the CGI Interface and Web Programming with REBOL

To understand CGI web programming, it's helps to have a little perspective. In mainstream computing, there are three ways users commonly interact with programs to input/return data. You've already seen examples of two methods:

1) The shell: data can be input/viewed directly in the REBOL interpreter. In short scripts, the "ask" and "print" words can be used to enter and display data. In this "command line" format, program flow can be manipulated by requesting text options from the user, and using conditional operations to react to input. Simple menus can be created by printing lists of choices to be selected by the user:

REBOL []

forever [
    prin "^(1B)[J"
    print "Select from the following options:^/"
    print "1 - Print message 1 time"
    print "2 - Print message 2 times"
    print "3 - Print message 3 times"
    print "4 - Quit^/"
    answer: to-integer ask "Your choice? "
    if answer = 4 [ask "^/Goodbye!  Press any key to end." quit]
    print ""
    loop answer [print "REBOL is great!"]
    print ""
    ask "Press [ENTER] to continue"
]

2) GUIs: within graphic user interfaces, text is entered and displayed in text fields, areas, and list views. Program flow is manipulated by responding to mouse clicks on graphic buttons, menu lists, and other widgets:

REBOL []

view layout [
    text "How many times should the message be displayed?"
    choice "1" "2" "3" [
        loop to-integer value [alert "REBOL is great!"]
    ]
]

3) CGI: this interface allows data to be input via a web page, using text fields, areas, and dropdown boxes in an html form. Data entered into a form is submitted and processed by the program that you create and store on your web server. Returned data is displayed as formatted text, tables, and other html elements that are dynamically created and output by your program.

There are two parts:

1) Html page:

<HTML><HEAD><TITLE>Data Entry Form</TITLE></HEAD><BODY>
<FORM ACTION="http://yourwebserver/yourrebolscript.cgi">
How many times should the message be displayed? <BR>
<INPUT TYPE="TEXT" NAME="times" SIZE="25">
<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
</FORM>
</BODY></HTML>

2) CGI script:

#!/home/youruserpath/REBOL -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Page title"</TITLE></HEAD><BODY>]
times: decode-cgi system/options/cgi/query-string
loop to-integer times/2 [print ["REBOL is great!" <BR>]]
print [</BODY></HTML>]

The first two options are typically used to create programs that run on a local computer. They're appropriate for programs that manipulate local user databases, games, audio/video applications, etc. The CGI option is useful when you want to create programs for the Internet.

All three methods above can be used to manipulate data stored online. You can, for example, write a program that uses a local shell or GUI interface to access and manipulate data stored on a web server. Multi-user business applications that share databases among employees at different locations, for example, are well suited to that type of client-server design. Each user gets a copy of the program, and everyone connects to a centralized data repository that exists at one location on a shared network.

To present data on the Internet, however, you don't always want to force users to download and install a local application. Contemporary computer users are familiar with custom database queries, web page searches, specialized email feedback forms, etc. that can be accessed directly on web pages. Rebol's built-in CGI interface enables this possibility, allowing the language to interact directly with information entered via web pages. Rebol's CGI ability allows you to write code that runs on a web server, receives data input from web pages, and returns data to the user's browser. In this way, html pages on your web site can form the complete user interface to programs you write. PHP, PERL and ASP are popular languages for such Internet programming, but if you know REBOL, you don't need to learn them. You can upload the REBOL interpreter to your web server and write cgi applications in the powerful and simple REBOL language syntax (just choose the REBOL version for the operating system running on your server). With CGI, users can interact entirely via a familiar web page interface, without ever knowing or caring that a program exists behind the scenes. That's one of the most common types of computer application in contemporary use. And REBOL makes it easy to create those types of programs.

The detailed mechanics of CGI programming are slightly beyond the scope of this tutorial, but the following crash course will get you started:

Learning html is required. That's true if you intend to attempt any type of Internet programming. Html is the layout language used to format text and GUI elements on all web pages. Html is not really a programming language - it doesn't let you manipulate data. It's just a simple markup format that allows you to shape the visual appearance of pages viewed in a browser.

In html, items on a web page are enclosed between starting and ending "tags":

<STARTING TAG>Some item to be included on a web page</ENDING TAG>

There are tags to effect the layout in every possible way. To bold some text, for example, do the following:

<STRONG>some bolded text</STRONG>

To create a table with three rows of data, do the following:

<TABLE border=1>
<TR><TD>First Row</TD></TR>
<TR><TD>Second Row</TD></TR>
<TR><TD>Third Row</TD></TR>
</TABLE>

Notice that every

<opening tag>

in the code above is followed by a corresponding

</closing tag>

Some tags surround all of the page, some tags surround portions of the page, and they're often nested inside one another to create more complex designs.

A minimal format to create a web page is shown below. Notice that the title is nested between head tags, and the entire document is nested within html tags. The page content seen by the user is surrounded by body tags:

<HTML><HEAD><TITLE>Page title</TITLE></HEAD><BODY>
A bunch of text and html formatting goes here...
</BODY></HTML>

If you save the above code to a text file called "yourpage.html", upload it to a web server, and surf to http://yourwebserver.com/yourpage.html , you'll see in your browser a page entitled "Page title", with the text "A bunch of text and html formatting goes here...". All web pages work that way - in fact, this very tutorial is an html document. Click View -> Source in your browser, and you'll see the html tags that are used to format the document.

You can create a CGI program on your web site that outputs the same web page above. Here's the format:

#!/home/youruserpath/REBOL -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Page title"</TITLE></HEAD><BODY>]

print ["A bunch of text and html formatting goes here..."]
print [</BODY></HTML>]

If you save the code above to a text file called "yourpage.cgi", upload it to a web server where you have REBOL installed in {youruserpath}, and surf to http://yourwebserver.com/yourpage.cgi , you'll see in your browser the exact same web page as in the previous example (to make it work, there will likely be some additional setup steps required to install REBOL - that's covered below). The first three lines in the code above are required to make your CGI scripts work properly. Memorize them by rote. The additional lines simply print out the html contained in the previous example. Simple, right?

That may seem like a lot of work to produce the same result as the previous example, but it enables the real power of CGI, which is to include dynamic, changeable information on your page. Say, for example, that you want to display the current time and date in the body of your web page. Html doesn't allow you do that, but the CGI program below does:

#!/home/youruserpath/REBOL -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Page title"</TITLE></HEAD><BODY>]
print ["The current date and time is: " now]
print [</BODY></HTML>]

The fifth line above contains the built in REBOL word "now", which is evaluated and printed on the web page. In the same way, you can read and print data that's been saved to the hard drive on your web server, you can perform calculations and other data manipulations such as conditional evaluations and loops, you can access databases, send emails, create and manipulate images and sounds, and do anything else that the programming language is capable of. Output data can be formatted as a nice web page that your user sees online.

The example below is a short program that reads and sorts the contents of a text file called "users.txt" and displays it on your web page:

#!/home/youruserpath/REBOL -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Page title"</TITLE></HEAD><BODY>]
if exists? %users.txt [data: sort read/lines %users.txt print data]
print [</BODY></HTML>]

If you want user data to be input into your CGI program, you need to create an html page with a "form". Html forms include text entry fields, dropdown selection boxes and other widgets that allow for data entry. The form should contain a single "action" that links to the web address of your CGI program. The form template below contains a text entry field and an action that points to http://yourwebserver/yourrebolscript.cgi .

<HTML><HEAD><TITLE>Data Entry Form</TITLE></HEAD><BODY>
<FORM ACTION="http://yourwebserver/yourrebolscript.cgi">
<INPUT TYPE="TEXT" NAME="username" SIZE="25">
<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">
</FORM>
</BODY></HTML>

To do something useful with the data sent by the above html form, you'll need to write a CGI program. That program will be stored at http://yourwebserver/yourrebolscript.cgi . To extract the information submitted by the form, include the following line in your REBOL CGI program:

a-word: decode-cgi system/options/cgi/query-string

You can use the assigned word above to refer to all the data sent when the html form was submitted. REBOL automatically assigns words to the data input in the html form. If you type "Fred Thompson" as the name in the text entry field on the form above, REBOL will decode it as:

[username: "Fred Thompson"]

You can use that data in a CGI program as follows:

#!/home/youruserpath/REBOL -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Page title"</TITLE></HEAD><BODY>]

username: decode-cgi system/options/cgi/query-string
print ["Hello " second username "!"]
print [</BODY></HTML>]

Just save that CGI code as http://yourwebserver/yourrebolscript.cgi , as referenced in the form action in the html page above. When you enter "Fred Thompson" in the html page, it sends the info to the CGI script, which displays a dynamically created web page saying "Hello Fred Thompson!".

Here's a simple cgi guestbook application that incorporates one of the most important techniques. It prints output which includes the html form layout. Because the form used to interact with the cgi program is printed by the cgi itself, no additional html page is required to make this script work. The action of the printed form points to the address of the CGI program itself. By evaluating whether the submitted data (decode-cgi system/options/cgi/query-string) is empty or not ("<> none"), the program can determine if any data has been submitted for processing. The program outputs a simple page that displays the names of all its former visitors, lets you enter your name, and then restarts the process by calling itself when you click submit:

#!/home/youruserpath/REBOL -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Guestbook"</TITLE></HEAD><BODY>]

print ["Here's a list of visitors who've signed this page:" ]
print [<BR>] ; prints a carriage return

; Check to see if a users.txt file has been created (the first
; time the script is run).  If not, create it before doing any
; processing:

either exists? %users.txt [
    newname: decode-cgi system/options/cgi/query-string
    ; check to see if any data has been entered (i.e., if the
    ; form has been submitted:
    if newname/2 <> none [
        ; if so, write the submitted text and a newline ("<BR>")
        ; to the users.txt file:
        write/append %users.txt join "<BR>" [newname/2]
    ]
    ; print all the names in the guestbook:
    signatures: read %users.txt
    print signatures
] [write %users.txt ""]

; Here's the form that gets printed to the page:
print [<FORM ACTION="http://yourwebserver/yourrebolscript.cgi">]
print [<BR><HR><BR>"Please enter your name:"<BR>]
print ["Name: "<INPUT TYPE="TEXT" NAME="username" SIZE="25">]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]
print [</BODY></HTML>]

Here's an example of a simple cgi "form mail" program. This is one of the most common types of web site applications. It prints a form to the browser, and when the user submits their information, it emails that information to the site owner. This example also demonstrates how to wrap the program in a template to make it look like other pages on your site:

#!/home/youruserpath/REBOL -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Email Us"</TITLE></HEAD><BODY>]

; The line below reads the top half of the template, and 
; prints it to the page.  Typically, this half of the template
; includes some formatting and design that matches the rest of
; your site.  It will also typically open a layout table, in
; which the printed form is enclosed:

print read %template_header.html

; Set the email settings and retrieve submitted data:

set-net [from_address@website.com  smtp.website.com]
submitted: decode-cgi system/options/cgi/query-string

; Process the form data, if any has been submitted:

if not empty? submitted [

    ; Create a message to be sent.  A time stamp is included here,
    ; which adjusts for the time difference between you and your
    ; web server.  For example, if you're in New York, and your
    ; server is in California, there's a 3 hour difference.  If
    ; you want the message marked with your local time, send the
    ; time "now + 3:00".  Notice that items from the submitted
    ; data are extracted using even numbered indexes (2, 4, 6):

    sent-message: rejoin [
        newline "INFO SUBMITTED BY WEB FORM" newline newline
        "Time Stamp: " (now + 3:00) newline
        "Name: " submitted/2 newline 
        "Email: " submitted/4 newline
        "Message: " submitted/6 newline 
    ]

    send/subject to_address@website.com sent-message "FORM SUBMISSION"

    ; The following code displays the data that was sent:

    html: make string! 2000
    emit: func [data] [repend html data]
    foreach [var value] submitted [
        emit [<TR><TD> mold var </TD><TD> mold value </TD></TR>]
    ]
    print [<font size=5>"Thank You!"</font> <br><br>]
    print ["The following information has been sent:" <BR><BR>]
    print rejoin ["Time Stamp: " now + 3:00]
    print [<BR><BR>]
    print [<table>]
    print html
    print [</table>]
    ; The line below reads the bottom half of the template, and 
    ; prints it to the page.  Typically, this half of the template
    ; closes the layout table and includes the closing </BODY> and
    ; </HTML> tags:

    print read %template_footer.html

    ; If info has been submitted, the program should quit before
    ; printing out the form again:

    quit
]

; Print the form if data has not been submitted:

print [<CENTER><TABLE><TR><TD>]
print [<BR><strong>"Please enter your info below:"</strong><BR><BR>]
print [<FORM ACTION="http://yourwebserver/yourrebolscript.cgi">]
print ["Name:" <BR> <INPUT TYPE="TEXT" NAME="name"><BR><BR>]
print ["Email:" <BR> <INPUT TYPE="TEXT" NAME="email"><BR><BR>]
print ["Message:" <BR>]
print [<TEXTAREA cols=75 name=message rows=5></textarea> <BR><BR>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]
print [</TD></TR></TABLE></CENTER>]

; If the form is being printed, the previous conditional code has
; not been executed and therefore the footer has not yet been
; printed:

print read %template_footer.html

The template_header.html file can include any page formatting and static content. A basic layout may include something similar to the following:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD><TITLE>Page Title</TITLE>   
<META http-equiv=Content-Type content="text/html; 
    charset=windows-1252">
</HEAD> 
<BODY bgColor=#000000>
<TABLE align=center background="" border=0 
    cellPadding=20 cellSpacing=2 height="100%" width="95%">
<TR>
<TD background="" bgColor=white vAlign=top>

The footer should close any tables or tags opened in the header, and may include any static content that appears after the CGI:

</TD>
</TR>
</TABLE>
<TABLE align=center background="" border=0 
    cellPadding=20 cellSpacing=2 width="95%">
<TR>
<TD background="" cellPadding=2 bgColor=#000000 height=5>
<P align=center><FONT color=white size=1>Copyright © 2006 
    Yoursite.com.  All rights reserved.</FONT>
</P>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>

The next example dynamically creates a selectable html dropdown list. The list is filled with data read from the "users.txt" file created earlier. It uses a foreach loop to automatically insert each item ("

#!/home/youruserpath/REBOL -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Dropdown Lists"</TITLE></HEAD><BODY>]

submitted: decode-cgi system/options/cgi/query-string

if not empty? submitted [
    print rejoin ["NAME SELECTED: " submitted/2 <BR><BR>]
    selected: rejoin [
        "TIME/DATE SELECTED: "
        submitted/4 " " submitted/6 ", " submitted/8
    ]
    print selected
    quit
]

; Print the form:
print [<FORM ACTION="http://yourwebserver/yourrebolscript.cgi">]
print [" SELECT A NAME: " <BR> <BR>]
names: read/lines %users.txt
print [<select NAME="names">]
foreach name names [prin rejoin ["<option>" name]]
print [</option> </select> <br> <br>]

print " SELECT A DATE AND TIME: "
print rejoin ["(today's date is " now/date ")" <BR><BR>]

print [<select NAME="month">]
foreach m system/locale/months [prin rejoin ["<option>" m]]
print [</option> </select>]

print [<select NAME="date">]    
for daysinmonth 1 31 1 [prin rejoin ["<option>" daysinmonth]]
print [</option> </select>]
print [<select NAME="time">]
for time 10:00am 12:30pm :30 [prin rejoin ["<option>" time]]
for time 1:00 10:00 :30 [prin rejoin ["<option>" time]]
print [</option> </select> <br> <br>]

print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]

The "users.txt" file used in the above example may look something like this:

nick
john
jim
bob

If you memorize the basic syntax, and become familiar with the techniques demonstrated in the previous few examples, you're well on your way to being able to program dynamic web applications with REBOL. The two-part template required to create CGI programs is the same regardless of how complex a program you create:

  1. Design an html web page with a form that gets all the input required from the user. Point the form action to the url of your CGI script.
  2. Design a REBOL CGI program to process the data input via the web form. The first three lines should be the same as the format shown in the template. Use "decode-cgi system/options/cgi/query-string" to automatically assign variable words to the data entered on the web form. To provide program output, print html formatted content to the user's browser using standard "print" statements. You can include an html form in the formatted output to call another CGI application.

Learning to use CGI comfortably requires a working knowledge of html. The required basics are very easy to learn. You can study enough html in one day to begin programming CGI applications in REBOL. Understanding how html forms work is the most important part. Try the search below for more information:

http://www.google.com/search?q=html+tutorial

REBOL CGI programming is covered in greater depth at the following links:

http://rebol.com/docs/cgi1.html

http://rebol.com/docs/cgi2.html

http://rebol.com/docs/cgi-bbs.html

http://www.rebol.net/cookbook/recipes/0045.html

The site below provides a free downloadable web server program written entirely in Rebol:

http://plain.at/vpavlu/plain-dev/r80v5/r80v5.html

It provides an alternative to CGI programming that lets you include REBOL code directly in your html pages, using a format similar to the PHP programming language. Using the above server, you can include REBOL code to be evaluated directly in your web pages using the following syntax:

<?REBOL {REBOL code to be evaluated} ?>

For example:

<HTML>
<BODY>
The time is currently: <?REBOL print now/time ?>.
</BODY>
</HTML>

That way, a CGI interface isn't even required. It's a convenient option if you're running your own web server.

27. Database Concepts - Using MySQL

Rebol's block/series data structure is a fantastic design that allows you to easily store and organize all types of data within programs. Blocks can be easily sorted, searched, reorganized, written to files, shared across networks, and manipulated in ways that are easy to understand and use. There are some situations, however, in which a larger database storage system is desirable to help manage significant loads of information. That's especially true in situations where multiple users access shared data. When writing scripts that work on web sites, for example, performance demands often require a database solution. Web sites commonly receive thousands/millions of visitors per day, all accessing and manipulating shared volumes of data simultaneously. Managing this type of activity can cause some hairy programmatic problems. For example, if you design a CGI program that reads and manipulates blocks of data from a text file on a web server, it's likely that many visitors may try to read, write and manipulate data in the file simultaneously. As each user reads and writes their own unique version of the text, an incomplete view of the current data is seen by each user. And worse, as each user writes their copy of the data back to a single text file, they'll likely overwrite and erase changes that other users have made. Additionally, using text files to store large amounts of data can result in slow performance for the user.

To solve these problems and more, there are a wide variety of database systems on the market. Databases manage all the difficult details of storing, sorting, searching for, and sharing the data they contain. Because manipulating data is at the heart of most computer applications, database systems are a cornerstone of modern program design. One of the most popular databases is "MySQL". Commercial web hosts typically provide free MySQL databases preconfigured and ready to use in your hosting account. The MySQL server software itself is also free to download and use on your own computer, your network, your website, in commercial products, etc. The MySQL server software is fast, powerful, and reliable, and it's been proven effective for many years, around the world. There are literally millions of web sites and applications that rely on MySQL as a database foundation.

The free versions of REBOL don't provide any native support that allow you to work with MySQL databases, but a free alternative has been created. As with Pascal Hurni's Dll patch, the free MySQL module was created by a third party developer, "Doc" Kimbel, and is not distributed or supported by the REBOL company. Doc's MySQL module is available at http://softinnov.org/rebol/mysql.shtml. It's a standard code module, written in native REBOL, that allows you to connect with and easily manipulate data in a MySQL server. Just download and "do" the code, or include it directly in your scripts (compress and embed it, etc.). A module for the "postgre" database server is also free to download from the same web site. There are other modules in the REBOL code library (http://rebol.org) which provide access to popular database systems such as SQLite. The commercial versions of REBOL also include native access to industrial database systems such as Oracle, MSSQL, and ODBC systems. Those versions of REBOL are recommended for commercial work. But for the purposes of this tutorial, a MySQL example will provide useful knowledge and skills necessary to build functional database apps and web sites.

27.1 Setting up and using MySQL

Using Doc's MySQL module, you can connect to a database on your computer, your network, your web server, etc. and send commands to it via a network connection. You can also transfer and manipulate data to/from the database using native REBOL series commands and block syntax, so that the information format is natural to use in your REBOL programs. Detailed instructions for the MySQL module are included in the package download, but if you've never used a database before, some additional setup information and usage concepts may be helpful.

First, you'll need to create a MySQL database. If you want to do that on your web site, you'll need to follow the instructions provided by your web host. Typically, your hosting account will provide some sort of general control panel that allows you to adjust all aspects of your web site's operation (email account settings, traffic logs, etc.). Typically, the web site control panel will provide an option to create a new MySQL database name, username, and password. If not, look for a "phpMyAdmin" link. You may also need to configure "remote access" hosts to allow the database to be used offsite. That's useful when you want to access a database hosted on your web site, from within scripts that reside on your home computer. If you're required to allow remote hosts, select the recommended wild card character, or type in the IP address of your local computer - this is not required if the database will only be accessed via scripts/CGIs that run on your web site. If you're not sure how to configure these options, talk to your web site administrator or your hosting provider, or read your hosting company's FAQs about MySQL. These settings are typically very easy to make and they only take a minute.

If you'd like to use a MySQL server on your own computer or on a private network, it's best to try one of the freely available installation packages that automatically install and configure a local database/web server for you. My preference for this type of install package is http://www.uniformserver.com. Just download and unzip the package, then click "DiskStart.vbs" and "Run MySQL". To create a local database, click "phpMyAdmin" and type in the name of a database to create. The default username and password for the uniformserver is "root" and "root".

Once you've created a MySQL database, either on your web site or on your home computer, "do" Doc's code module:

do mysql-107.rip  
; This unpacks the compressed "rip" file.  This step only needs to
; be done the first time you use the package on a given computer.
do %mysql-r107/mysql-protocol.r  
; This loads the module, and needs to be done every time you access
; a MySQL database.
; At the time of this writing, 1.07 was the most current version of
; the mysql module.  Update the numbers in the previous two lines
; of code to reflect the curent version number you've downloaded.

Next, enter the database info (location, username, and password), as in the instructions at http://softinnov.org/rebol/mysql-usage.html:

db: open mysql://username:password@yourwebsite.com/yourdatabasename

For example, if you're using the uniformserver on a local PC, the above line will read, by default, as follows:

db: open mysql://root:root@localhost/yourdatabasename

With that, you've got access to a data management engine that can handle many millions of records and many thousands of concurrent users. To use the database, you'll need to learn a bit about "tables" and the "SQL" command language.

27.2 Tables and SQL statements

Databases typically store information in tables. Tables are made up of columns of related data, which are filled with rows of unique data entries. A contact database, for example, may contain separate columns titled "name", "address", "phone number", and "birthday". For each new person added to the contact database, a row in each of those columns is typically added. Columns can be left empty. Here's an example contact database table:

name            address                 phone       birthday
----            -------                 --------    --------
John Smith      123 Toleen Lane         555-1234    1972-02-01
Paul Thompson   234 Georgetown Place    555-2345    1972-02-01
Jim Persee      345 Portman Pike        555-3456    1929-07-02
George Jones    456 Topforge Court                  1989-12-23
Tim Paulson                             555-5678    2001-05-16

You can add and remove tables to/from your database using the control panel or phpMyAdmin link on your web site (the same one you used to create the database). You can also do it directly in REBOL, using the format below:

insert db {create table Contacts (
    name            varchar(100),
    address         text,
    phone           varchar(10),
    birthday        date 
)}

Each line of code above consists of a column name, along with a data type to be stored in that column. The "create table" portion of the code above is an "SQL" command. Most modern databases use some variation of the SQL language to control the way tables and data are inserted, removed, sorted, and otherwise manipulated within the database. You need to know the syntax of various MySQL commands to get the database to perform those operations correctly. A quick Google search will reveal many SQL tutorials, so it will only be covered briefly here. Here's a short synopsis of some important MySQL commands:

Statements that manipulate data:

SELECT - extract data from a table
INSERT INTO - insert new data into a table
UPDATE - alter or add to existing data in a table
DELETE - delete data from a table

Statements that create and delete tables:

CREATE TABLE - create a new table
ALTER TABLE  - change the column structure of a table
DROP TABLE   - delete a table
CREATE INDEX - create a search key
DROP INDEX   - delete a search key

The SELECT statement is one that you'll use most often. Its format is as follows:

SELECT column_name(s)
FROM table_name

To retrieve all the data from the Contacts table, use the code below:

SELECT * FROM Contacts

To retrieve all the data from the name and address columns of the Contacts table:

SELECT name,address FROM Contacts

To retrieve all the data from the birthday column of the Contacts table, without any duplicates (i.e., 1972-02-01 would only be returned once):

SELECT DISTINCT birthday FROM Contacts

To perform conditional searches, use the WHERE clause. The following code will return the entire row of data for John Smith from the Contacts table:

SELECT * FROM Contacts WHERE name='John Smith'

When using the WHERE clause, search text should be entered in single quotes and the following conditional operators can be used:

=       Equal
<>      Not equal
>       Greater than
<       Less than
>=      Greater than or equal
<=      Less than or equal
BETWEEN Within an inclusive range
LIKE    Search for a pattern

When using the LIKE condition, a "%" sign can be used for wildcards (missing letters in a pattern). The following line will return the complete rows of data for John Smith and Jim Persee (names that begin with "J"):

SELECT * FROM Contacts WHERE name LIKE 'J%'

The following line will return a result set containing the info for John Smith, Paul Thompson, and Jim Persee (birthdays that contain the characters "02")

SELECT * FROM Contacts WHERE birthday LIKE '%02%'

You can use AND and OR in the WHERE clause to refine the search:

SELECT * FROM Contacts WHERE birthday LIKE '%72%' OR phone LIKE '%34'

The IN clause lets you specify as list of data to match with a column:

SELECT * FROM Contacts WHERE phone IN ('555-1234','555-2345')

The BETWEEN...AND clause allows you to specify matches within a range:

SELECT * FROM Contacts
    WHERE birthday NOT BETWEEN '1900-01-01' AND '2010-01-01'

To sort result sets, use the ORDER BY clause. The following line returns all the info in the Contact table, in alphabetical order by name:

SELECT * FROM Contacts ORDER BY name

To reverse the order, use the DESC command. The following line returns data from the name and birthday columns, by order of birthday. When two records contain the same birthday, they're ordered next by descending alphabetical name:

SELECT name, birthday FROM Contacts ORDER BY birthday, name DESC

Other SQL statements:

Where SELECT retrieves data from exiting table columns, INSERT puts new data into columns. The format for inserting data is as follows:

INSERT INTO table_name VALUES (value1, value2,....)

The following code adds a new row to the Contacts table:

INSERT INTO Contacts 
    VALUES ('Billy Powell', '5 Binlow Dr.', '555-6789', '1968-04-19')

You can also specify the columns in which data should be created:

INSERT INTO Contacts (name, phone) 
    VALUES ('Robert Ingram', '555-7890')

The UPDATE statement is used to alter or add data to an existing column. The following line will add an address for Robert Ingram:

UPDATE Contacts SET address = '643 Pine Valley Rd.' 
    WHERE name = 'Robert Ingram'

The DELETE statement removes data from a table. The following code will remove the entire row of information for John Smith:

DELETE FROM Contacts WHERE name = 'John Smith'

To erase all the data in the Contacts database, without destroying the table:

DELETE * FROM Contacts

There is more to learn about SQL, including JOINS, indexes, and functions, but the statements presented so far should provided a basic understanding to build upon. For more info, perform a Google search on "MySQL tutorial" or "SQL tutorial".

27.3 Using SQL Statements in REBOL code

SQL commands work directly in the database engine, and Doc's module makes that data directly available within your program, in typical REBOL blocks. To integrate SQL statements in your REBOL code, enclose them as follows:

insert db {SQL command}

To retrieve the result set created by any inserted command, use:

copy db

To retrieve only the first result of any command, use:

first db

To retrieve a portion of the results from any command, use the following format:

probe copy/part db 3    ; get 3 records from the result

When you're done using the database, close the connection:

close db

27.4 Putting It All Together

Here's a complete example that opens a database connection, creates a new Contacts table, inserts data into the table, makes some changes to the table, and then retrieves and prints all the contents of the table, and closes the connection:

REBOL []

do %mysql-protocol.r 
db: open mysql://root:root@localhost/Contacts
; insert db {drop table Contacts} ; erase the old table if it exists
insert db {create table Contacts (
    name            varchar(100),
    address         text,
    phone           varchar(12),
    birthday        date 
)} 
insert db {INSERT into Contacts VALUES 
    ('John Doe', '1 Street Lane', '555-9876', '1967-10-10'),
    ('John Smith', '123 Toleen Lane', '555-1234', '1972-02-01'),
    ('Paul Thompson', '234 Georgetown Pl.', '555-2345', '1972-02-01'),
    ('Jim Persee', '345 Portman Pike', '555-3456', '1929-07-02'),
    ('George Jones', '456 Topforge Court', '', '1989-12-23'),
    ('Tim Paulson', '', '555-5678', '2001-05-16')
}
insert db "DELETE from Contacts WHERE birthday = '1967-10-10'"
insert db "SELECT * from Contacts"
results: copy db
probe results
close db
halt

27.5 Short Format

Here's a short coding format enabled in Doc's MySQL module that can be used to work with database tables quickly and easily:

read/custom join mysql://user:pass@host/DB/ ["SELECT * from DB"]

You can use that format to issue statements to any MySQL database. It's a very simple syntax that allows you to deal with massive amounts of data, using all the power of the SQL language and the MySQL server software. This single line syntax provides access to multiple tables of information in multiple databases, and enables a solution for some of the most complex data management requirements you're ever likely to encounter. The following line, for example, could potentially return millions of rows of data in a broad multiuser Internet environment without any complex coding required:

foreach row read rejoin [mysql://root:root@localhost/Contacts/ 
    ["SELECT * from Contacts"]] [print row]

Just issue the SQL statement, and the database does the rest.

It's important to understand that you can use any native REBOL functions to manipulate result set blocks returned by the SQL commands. You can use the derived data blocks in your program just as if they had been created using any native REBOL means. Here's an example that puts the results from an SQL query into a GUI text list, which in turn allows you to click and select an item from the list. When an item is selected, the full row of results is displayed in a GUI area widget, with each column formatted and labeled:

REBOL []

do %mysql-protocol.r 
db: open mysql://root:root@localhost/Contacts
insert db "SELECT * from Contacts"
results: copy db 
view layout [
    text-list 100x400 data results [
        ; When an item in the list is clicked,
        ; display its data in a text area:
        string: rejoin [
            "NAME:      " value/1 newline
            "ADDRESS:   " value/2 newline
            "PHONE:     " value/3 newline
            "BIRTHDAY:  " value/4
        ]
        view/new layout [
            area string
        ] 
    ] 
]
close db

Here's the same example using short format:

REBOL []

results: read rejoin [
    mysql://root:root@localhost/Contacts? "SELECT * from Contacts"]
view layout [
    text-list 100x400 data results [
        string: rejoin [
            "NAME:      " value/1 newline
            "ADDRESS:   " value/2 newline
            "PHONE:     " value/3 newline
            "BIRTHDAY:  " value/4
        ]
        view/new layout [
            area string
        ] 
    ] 
]

The example below is a full MySQL CGI application. It allows users to check attendance at various weekly events, and add/remove their names from each of the events. By using MySQL to store the data, there are no concerns about whether the script will be accessed by multiple visitors simultaneously. The speed of the data transactions will also contintue to be very fast, even if millions of people access the site every day and cause the database to grow very large. This example can be used as a prototype for many kinds of useful web applications in which users add, remove, and view data in an online database, directly from forms on a web page:

REBOL []

do %/home/path/public_html/rebol/mysql-r107/mysql-protocol.r 
db: open mysql://user:pass@website.com/user_db
; insert db {drop table jamsignup}  ; erase the old table if it exists
insert db {create table jamsignup (
    event           varchar(150),
    date            date,
    time            varchar(100),
    teachers        text,
    students        text,
    notes           text
)} 

; Each entry should go on a single line.  These lines are broken
; apart to maintain this web page's formatting:

insert db {INSERT into jamsignup VALUES 
    ('Class', '2006-10-1', '4:00pm', 
        '[Nick Antonaccio] [Ryan Gaughan] [Mark Carson]', '', ''),
    ('Class', '2006-10-8', '4:00pm', 
        '[Nick Antonaccio] [Ryan Gaughan] [Mark Carson]', '', ''),
    ('Class', '2006-10-15', '4:00pm', 
        '[Nick Antonaccio] [Ryan Gaughan] [Mark Carson]', '', ''),
    ('Class', '2006-10-22', '4:00pm', 
        '[Nick Antonaccio] [Ryan Gaughan] [Mark Carson]', '', ''),
    ('Class', '2006-10-29', '4:00pm', 
        '[Nick Antonaccio] [Ryan Gaughan] [Mark Carson]', '', ''),
    ('Class', '2006-11-5', '4:00pm', 
        '[Nick Antonaccio] [Ryan Gaughan] [Mark Carson]', '', '')
}
insert db "SELECT * from jamsignup"
results: copy db
probe results

response: ask "add, remove, or list?  "
event:      "Class"
date:       "2006-12-1"
time:       "4:00pm"
teachers:   "[Nick Antonaccio] [Ryan Gaughan] [Mark Carson]"
students:   "John Smith"
notes:      ""

query: rejoin [{INSERT into jamsignup VALUES } {('} event {
    ', '} date {', '} time {', '} teachers {', '} students {
    ', '} notes {')}]

switch/default response [
    "add"       [
        insert db query
    ]
    "remove"    [print "remove"]
    "list"      [print "list"]
] [print "that's not one of the choices."]


foreach row read rejoin [
    mysql://user:pass@website.com/user_db? "SELECT * from jamsignup"
] [print row]
print newline
query: rejoin [
    {DELETE FROM jamsignup WHERE students = '} students {'}
]
insert db query
foreach row read rejoin [
    mysql://user:pass@website.com/user_db? "SELECT * from jamsignup"
] [print row]


close db
halt

For comparison, here is a version of the same script that doesn't use MySQL, but rather stores the data in a flat text file ("jam.db"). Comparing it to the above example will help you understand the coding differences required to implement and store data in MySQL:

#! /home/path/public_html/rebol/REBOL -cs
REBOL [title: "event.cgi"]
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Event Sign-Up"</TITLE></HEAD><BODY>]

jams: load %jam.db

a-line: [] loop 65 [append a-line "-"]
a-line: trim to-string a-line

print [<hr> <font size=5>" Sign up for an event:"</font> <hr><BR>]
print [<FORM ACTION="http://yourwebsite.com/cgi-bin/event.cgi">]
print [" Student Name: "]
print [<input type=text size="50" name="student"><BR><BR>]
print [" ADD yourself to this event:             "]
print [<select NAME="add"><option>""<option>"all"]
foreach jam jams [prin rejoin ["<option>" jam/1]]
print [</option> </select> <BR> <BR>]
print [" REMOVE yourself from this event: "]
print [<select NAME="remove"><option>""<option>"all"]
foreach jam jams [prin rejoin ["<option>" jam/1]]
print [</option> </select> <BR> <BR>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]

print-all: does [
    print [<br><hr><font size=5>]
    print " Currently scheduled events, and current attendance:"
    print [</font><br>]
    foreach jam jams [
        print a-line
        print [<BR>]
        print rejoin ["" jam/1]
        print [<BR>]
        print a-line
        print [<BR>]
        for person 2 (length? jam) 1 [
            print jam/:person
            print [<BR>]
        ]
        print [<BR>]
    ]
    print [</BODY></HTML>]
]

selection: decode-cgi system/options/cgi/query-string

if selection/2 <> none [
    if ((selection/4 = "") and (selection/6 = "")) [
        print [<strong>]
        print "Please try again. You must choose an event."
        print [</strong>]
        print-all
        quit
    ]
    if ((selection/4 <> "") and (selection/6 <> "")) [
        print [<strong>]
        print "Please try again. Choose add OR remove."
        print [</strong>]
        print-all
        quit
    ]
    if selection/4 = "all" [
        foreach jam jams [append jam selection/2]
        save %jam.db jams
        print [<strong>]
        print "Your name has been added to every event: "
        print [</strong>]
        print-all
        quit
    ]
    if selection/6 = "all" [
        foreach jam jams [
            if find jam selection/2 [
                remove-each name jam [name = selection/2]
                save %jam.db jams
            ]
        ]
        print [<strong>]
        print "Your name has been removed from all events: "
        print [</strong>]
        print-all
        quit
    ]
    foreach jam jams [
        if (find jam selection/4) [
            append jam selection/2
            save %jam.db jams
            print [<strong>]
            print "Your name has been added to the selected event: "
            print [</strong>]
            print-all
            quit    
        ]
    ]
    found: false
    foreach jam jams [
        if (find jam selection/6) [
            if (find jam selection/2) [
                remove-each name jam [name = selection/2]
                save %jam.db jams
                print [<strong>]
                print "Your name has been removed "
                print "from the selected event: "
                print [</strong>]
                print-all
                quit
                found: true
            ]
        ]
    ]
    if found <> true [
        print [<strong>]
        print "That name is not found in the specified event!"
        print [</strong>]
        print-all
        quit
    ]
]

print-all

Here is a sample of the "jam.db" datafile used in the above example:

["Sunday September 16, 4:00 pm - Jam CLASS"
    "Nick Antonaccio" "Ryan Gaughan" "Mark Carson"]
["Sunday September 23, 4:00 pm - Jam CLASS"
    "Nick Antonaccio" "Ryan Gaughan" "Mark Carson"]
["Sunday September 30, 4:00 pm - Jam CLASS"
    "Nick Antonaccio" "Ryan Gaughan" "Mark Carson"]

The following line of code could be used to automatically create a year's worth of entries for the above data file:

for date (now/date - 1) (now/date + 365) 7 [
    print rejoin [{["} date {, 4:00 pm - Jam CLASS" ""]}]]

28. Parsing

The built-in function "parse" is an important part of the REBOL language. It's used to import and convert organized chunks of external data into the block format that REBOL recognizes natively. It also provides a means of dissecting, searching, comparing, extracting, and acting upon organized information within unformatted text data. Its pattern matching abilities are similar to regular expressions, found in Perl and other languages. As with many things, the REBOL way is powerful, elegant and simple to use.

The basic format for parse is:

parse <data> <matching rules>

Parse has several modes of use. The simplest mode just splits up text at common delimiters and converts those pieces into a REBOL block. To do this, just specify "none" as the matching rule. Common delimiters are spaces, commas, tabs, semicolons, and newlines. Here are some examples:

text1: "apple orange pear"
parsed-block1: parse text1 none

text2: "apple,orange,pear"
parsed-block2: parse text2 none

text3: "apple       orange                  pear"
parsed-block3: parse text3 none

text4: "apple;orange;pear"
parsed-block4: parse text4 none

text5: "apple,orange pear"
parsed-block5: parse text5 none

text6: {"apple","orange","pear"}
parsed-block6: parse text6 none

text7: {
apple
orange  
pear
}
parsed-block7: parse text7 none

All of the above parsed blocks evaluate to ["apple" "orange" "pear"]. That's useful because it enables easy importing of data files created by other programs. You could, for example, use the "none" rule to import CSV files created by a spreadsheet or database application. The data is automatically converted to native REBOL data blocks for use in your scripts.

If you need to split files based on some character other than the common delimiters, you can specify the delimiter as a rule. Just put the delimiter in quotes:

text: "apple*orange*pear"
parsed-block: parse text "*"

text: "apple&orange&pear"
parsed-block: parse text "&"

text: "apple    &    orange&pear"
parsed-block: parse text "&"

You can also include mixed multiple characters to be used as delimiters:

text: "apple&orange*pear"
parsed-block: parse text "&*"

text: "apple&orange*pear"
parsed-block: parse text "*&" ; the order doesn't matter

All of the above parsed blocks also evaluate to the same ["apple" "orange" "pear"]. Using the "splitting" mode of parse is a great way to get formatted tables of data into your REBOL programs.

Splitting the text below by carriage returns, you run into a little problem:

text: { First Name
        Last Name
        Street Address
        City, State, Zip}

parsed-block: parse text "^/" 

; ^/ is the REBOL symbol for a carriage return

Spaces are included in the parsing rule by default (parse automatically splits at all empty space), so you get a block of data that's more broken up than intended:

["First" "Name" "Last" "Name" "Street" "Address" "City,"
    "State," "Zip"]

You can use the "/all" refinement to eliminate spaces from the delimiter rule. The code below:

text: { First Name
        Last Name
        Street Address
        City, State, Zip}

parsed-block: parse/all text "^/"

converts the given text to the following block:

[" First Name" "      Last Name" "      Street Address"
    "      City, State, Zip"]

Now you can trim the extra space from each of the strings:

foreach item parsed-block [trim item]

and you get the following parsed-block, as intended:

["First Name" "Last Name" "Street Address" "City, State, Zip"]

The example above could be adjusted to pull unformatted lines of info from any text file into your REBOL application. But that's not all parse can do. You can also use it to check whether any specific data exists within a given block. To do that, specify the rule (matching pattern) as the item you're searching for. Here's an example:

parse ["apple"] ["apple"]

parse ["apple" "orange"] ["apple" "orange"]

Both lines above evaluate to true because they match exactly. IMPORTANT: By default, as soon as parse comes across something that doesn't match, the entire expression evaluates to false, EVEN if the given rule IS found one or more times in the data. For example, the following is false:

parse ["apple" "orange"] ["apple"]

But that's just default behavior. You can control how parse responds to items that don't match. Adding the words below to a rule will return true if the given rule matches the data in the specified way:

  1. "any" - the rule matches the data zero or more times
  2. "some" - the rule matches the data one or more times
  3. "opt" - the rule matches the data zero or one time
  4. "one" - the rule matches the data exactly one time
  5. an integer - the rule matches the data the given number of times
  6. two integers - the rule matches the data a number of times included in the range between the two integers

The following examples are all true:

parse ["apple" "orange"] [any string!]
parse ["apple" "orange"] [some string!]
parse ["apple" "orange"] [1 2 string!]

You can create rules that include multiple match options - just separate the choices by a "|" character and enclose them in brackets. The following is true:

parse ["apple" "orange"] [any [string! | url! | number!]]

You can trigger actions to occur whenever a rule is matched. Just enclose the action(s) in parentheses:

parse ["apple" "orange"] [any [string! 
    (alert "The block contains a string.") | url! | number!]]

You can skip through data, ignoring chunks until you get to, or past a given condition. The word "to" ignores data UNTIL the condition is found. The word "thru" ignores data until JUST PAST the condition is found. The following is true:

parse [234.1 $50 http://rebol.com "apple"] [thru string!]

The real value of pattern matching is that you can search for and extract data from unformated text, in an organized way. The word "copy" is used to assign a variable to matched data. For example, the following code downloads the raw html from the REBOL homepage, ignores everything except what's between the html title tags, and displays that text:

parse read http://rebol.com [
    thru <title> copy parsed-text to </title> (alert parsed-text)
]

The following code extends the example above to provide the useful feature of displaying the external ip address of the local computer. It reads http:/whatsmyip.org, parses out the title text, and then parses that text again to return only the IP number. The local network address is also displayed, using the built in dns protocol in Rebol:

parse read http://whatsmyip.org/ [
    thru <title> copy my-ip to </title>
]
parse my-ip [
    thru "Your IP is " copy stripped-ip to end
]
alert to-string rejoin [
    "External: " trim/all stripped-ip "  "
    "Internal: " read join dns:// read dns://
]

Here's a useful example that removes all comments from a given REBOL script (any part of a line that begins with a semicolon ";"). This code is based on the script at http://www.rebol.org/library/scripts/uncomment.r . First it asks for the file name, and assigns the contents of that file to the variable word "code". Using the "to" and "thru" words, the content is then parsed for any text that begins with the ";" character, and ends with a newline. The action block of the parse rule uses the built-in "remove/part" function to remove that parsed text from the code. Finally, the parsed code is send to the built-in REBOL text editor for viewing, saving, etc.:

code: read to-file request-file
parse/all code [any [
    to #";" begin: thru newline ending: (
        remove/part begin ((index? ending) - (index? begin))) :begin
    ]
]
editor code

To learn more about mining raw data with parse, see the following links:

http://www.codeconscious.com/rebol/parse-tutorial.html

http://www.rebol.com/docs/core23/rebolcore-15.html

http://en.wikibooks.org/wiki/REBOL_Programming/Language_Features/Parse

http://www.rebolforces.com/zine/rzine-1-06.html#sect4.

29. REBOL Objects

REBOL objects are code structures that allow you to encapsulate and replicate code. They provide the ability to bundle packages of functions and data together so that they can be easily copied, reused, and accessed as self-contained units. REBOL objects provide an easy way to design reusable templates of related code that can be reproduced and varied to create new code units whose contents automatically default to a predefined design. Objects also provide a way to create clones of clones of objects, so that any object code can serve as the basis for a new object design. In this way, the concept of object cloning can be extended for multiple generations, to create variations upon variations of code derived from a single template.

Objects are primarily useful in REBOL when creating duplicatable data structures and when implementing multiple copies of similar code. Objects are also useful because they hide internal data and functions within their structure. They can help ensure that words used in one context are not mistakenly used in another context within the same program. Managing word usage in this way (hiding and exposing word definitions within various sections of a program) is referred to as "namespace" management.

Prototype blueprints are used in REBOL to easily build data objects that duplicate ("inherit") and change ("override") a basic code outline. Creating a user account model is a good example of how this might be useful. You can create a general account prototype, with a standard set of data fields and user functions that are part of every customer's profile. Then for each new customer, the prototype model can be copied, with all the internal data fields inherited. Any fields that are unique for each individual user can be changed (overridden) to create a unique account instance.

Note: Unlike other programming languages in which most code is conceived and organized in terms of object constructs, REBOL provides a small subset of tools that allow you to simply clone code blueprints. "Classes" are a fundamental premise found in other object oriented languages, but they don't exist in REBOL in the same way. If you're familiar with other object oriented programming languages, be aware that object model ("class") properties in REBOL are simply copied, rather than truly inherited. The difference is that changes to a "parent" object prototype model in REBOL are not passed on to its derived child objects, once the child objects are created.

To make an original object blueprint in REBOL, use the following syntax:

label: make object! [object definition]

The object definition can contain functions, values, and/or data of any type.

Here's a real world example that defines a blank user account object. It contains 6 variable words (note that each word is set to equal the next word, which is in turn eventually cascaded down to equal "none"):

account: make object! [
    first-name: 
    last-name:
    address:
    phone:
    email-address:
    none
]

The account definition above simply wraps the 6 variables into a container, or context, called "account".

Simple, right?

You can refer to data and functions within an object using refinement ("/path") notation:

object/word

In the example above, "account/phone" refers to the phone number data contained in the account. You can make changes to elements in an object as follows:

object/word: data

In the example above:

account/phone: "555-1234"
account/address: "4321 Street Place Cityville, USA 54321"

In many OO languages, the "dot" notation is used to refer to things contained in an object (i.e., "methods" and "properties"), using the format "object.method". If you've learned another OO language before learning REBOL, converting the dot to a slash in your mind will help you make an intuitive transition to REBOL object notation.

Once an object is created, you can view all its contents using the built-in word "help":

help object

; or

? object  

; "?" is a shorthand synonym for the word "help"

If you've typed in all the account examples so far into the REBOL interpreter, then:

? account

displays the following info:

ACCOUNT is an object of value:
   first-name      none!     none
   last-name       none!     none
   address         string!   "4321 Street Place Cityville, USA 54321"
   phone           string!   "555-1234"
   email-address   none!     none

Once you've created an object prototype, you can make a new object based on the original prototype definition. The new object will contain all the data and functions defined in the original prototype. To create such a cloned object, use the following syntax:

label: make existing-object [
    values to be changed from the original prototype definition
]

For example, the code below creates a new account block labeled "user1". You can change any part of the original object block definition, redefine the variables, etc., as follows:

user1: make account [
    first-name: "John"
    last-name: "Smith"
    address: "1234 Street Place  Cityville, USA 12345"
    email-address: "john@hisdomain.com"
]

Notice that the phone number variable is not set in the above example. In this case, the phone number variable retains the default value of "none" established in the original account definition. This is one of the key benefits of using objects. Our example account object only contains 6 variables, so very little time was saved when defining the user1 account (only the phone number field was duplicated). If, however, the parent account object contained 20 default variables, for example, a great deal of time would be saved when creating each derived user account. For example, if user account was designed to assign an automatic email address, folder name, and other default variables for users at a web hosting service, the object model would be very useful in automating the account setup process.

In addition to changing existing variables in an object definition, you can also extend the existing object definition with new values:

label: make existing-object [new-values]

The definition below creates a new account object, redefines all the existing variables, and appends a new variable to hold the user's favorite color.

user2: make account [
    first-name: "Bob"
    last-name: "Jones"
    address: "4321 Street Place Cityville, USA 54321"
    phone:  "555-1234"
    email-address: "bob@mysite.net"
    favorite-color: "blue"
]

"user2/favorite-color" now refers to "blue" ("user1/favorite-color" is still not defined - you'll get an error if you try to access the favorite-color path of user1).

The code below creates a duplicate of the user2 account, with only the name changed:

user2a: make user2 [
    first-name: "Paul"
    email-address: "paul@mysite.net"
]

"? user2a" provides the following info:

USER2A is an object of value:
   first-name      string!   "Paul"
   last-name       string!   "Jones"
   address         string!   "4321 Street Place Cityville, USA 54321"
   phone           string!   "555-1234"
   email-address   string!   "paul@mysite.net"
   favorite-color  string!   "blue"

You can create any number of user profiles based on the "account" template. Any field that isn't filled in will retain the default value of "none", established in the template definition.

With the above user objects defined, you can perform comparisons or any other operations that reference the enclosed data, such as:

if user1/address <> user2/address [
    print "Those users live at different locations."
]

Now, here's the real kicker. You can also include functions in your object definition:

account: make object! [
    first-name: 
    last-name:
    address:
    phone:
    none
    email-address: does [
        return to-email rejoin [
            first-name "_" last-name "@website.com"
        ]
    ]
    display: does [
        print ""
        print rejoin ["Name:     " first-name " " last-name]
        print rejoin ["Address:  " address]
        print rejoin ["Phone:    " phone]
        print rejoin ["Email:    " email-address]
        print ""
    ]
]

user1: make account []

user2: make account [
    first-name: "John"
    last-name: "Smith"
    phone:  "555-4321"
]

user3: make account [
    first-name: "Bob"
    last-name: "Jones"
    address: "4321 Street Place Cityville, USA 54321"
    phone:  "555-1234"
    email-address: "bob@mysite.net"
]

; to print out all the data contained in each object:

user1/display user2/display user3/display

It's important to see here that the original account object definition includes the "display" function. The account prototype also defines all the variables to be "none" by default. All that code is part of the prototype definition, so each new object instance (user1, user2, user3) contains all that code by default. As a result, the user1 object, for example, is a fully functional object even though no variables or functions were defined during its creation. All it takes to create that object is the code "user1: make account []".

It's also important to recognize that in each user instance, the variables printed by the display function are only those local to the given instance. For example, when the user3/display function is called, it prints the variables contained in the user3 object. None of the variables contained in user1 or user2 get unintentionally mixed in, even though they're referred to by the same word. That's one of the primary benefits of objects - a group of variables and functions are encapsulated in an easily duplicatable object, and each object is self contained. The variables contained in any of the objects can be updated at any time using path notation (i.e., user1/first-name: "George").

Note that the variable "email-address" is initially assigned to a function, which defines the default email address to be "first-name_last-name@website.com". You can override that definition, as in the user/3 definition, to simply refer to an email address string. Once you've done that, the email-address function no longer exists. You could use a scheme such as the one above, for example, to add users with default profile outlines to a vendor/customer list, or for any other type of account management.

The next example sheds a bit more light on how objects are useful. It's a small game in which multiple character objects are created from a duplicated template. Each object contains some code and data that allows the user(s) to move several imaginary characters. Based on the user choice, the character's current position is updated. Five instances of characters are created, each with a different initial position property. The point of the game is to find the hidden prize and make one of the characters move to its location. Upon each user move, the position of the character object is checked to see if it matches the position of the hidden prize:

; Create a random coordinate at which the hidden prize
; will be found:

hidden-prize: random 15x15

; Here's the basic "character" prototype definition.
; It contains a variable that holds the character's
; current position.  It also contains a function that
; gets a movement choice from the user, then updates
; the character's current position, and displays
; a message if the hidden prize is found. 

character: make object! [
    position: 0x0
    move: does [
        direction: ask "Move up, down, left, or right:  "
        ; change the object's position, based on the user's
        ; movement choice:
        switch/default direction [
            "up" [position: position + -1x0]
            "down" [position: position + 1x0]
            "left" [position: position + 0x-1]
            "right" [position: position + 0x1]
        ] [print newline print "THAT'S NOT A DIRECTION!"]
        ; display a message if the hidden prize is found:
        if position = hidden-prize [
            print newline
            print "You found the hidden prize.  YOU WIN!"
            print newline
            halt
        ]
        ; print a summary of the movement and the new 
        ; location relative to the hidden prize:
        print rejoin [
            newline
            "You moved character " movement " " direction
            ".  Character " movement " is now " 
            hidden-prize - position
            " spaces away from the hidden prize.  "
            newline
        ]
    ]
]

; Now five character objects are created, each with a
; different initial position:

character1: make character[]
character2: make character[position: 3x3]
character3: make character[position: 6x6]
character4: make character[position: 9x9]
character5: make character[position: 12x12]

; The user gets 20 chances.  During each chance, the
; move function of the selected character is executed and
; the current position of all characters is displayed.

loop 20 [
    prin "^(1B)[J"
    movement: ask "Which character do you want to move (1-5)?  "
    ; to avoid errors, check that user entered one of the 
    ; character numbers "1-5":
    if find ["1" "2" "3" "4" "5"] movement [
        ; The line below will evaluate to "do character1/move"
        ; if character1 is chosen above, for example. 
        do rejoin ["character" movement "/move"]
        print rejoin [
            newline
            "The position of each character is now:  "
            newline newline
            "CHARACTER ONE:   " character1/position newline
            "CHARACTER TWO:   " character2/position newline
            "CHARACTER THREE: " character3/position newline
            "CHARACTER FOUR:  " character4/position newline
            "CHARACTER FIVE:  " character5/position
        ]
        ask "^/Press the [Enter] key to continue."
    ]
]

Using this type of object prototype definition, it would be just as easy to create 1000 or more characters, each containing a very complex replicated definition of functions and data structures. You could, for example, extend this concept to create a vast world of characters in an online multiplayer game. Each new player would just be a new instance of a character object. The actions that each player takes and the data he/she collects would simply be updated as path variables in their individual instance of the character object.

Another use for objects is to avoid namespace clashes that occur as a result of using the same word in more than one context. As you write more code, you may create a library of functions to accomplish common tasks. If you use the same word in any two functions to represent separate actions, that could cause a problem if you import and use the functions in the same program. If that happens, one function may unintentionally redefine a word that you use in another function, and cause it to perform in an unexpected way. An easy solution to such namespace clutter is to wrap your library functions in an object, and expose the function words you want in your program. That way, you can call the function in the context of the object's name, and not worry about namespace clashes. For example, in the code below, the first "bank" function and "var" variable get overwritten when those words are redefined a second time:

var: 1234.56
bank: does [
    print ""
    print rejoin ["Your bank account balance is:  $" var]
    print ""
]

var: "Wabash"
bank: does [
    print ""
    print rejoin [
        "Your favorite place is on the bank of the:  " var]
    print ""
]

bank

Evaluating "bank" at the end of the above code yields "Your favorite place is on the bank of the: Wabash". There's no way to access the bank account balance code at this point, because the words have been completely redefined. If such a redefinition of words had been done unintentionally in any code, it would likely cause an error. You can avoid that problem by simply wrapping the above code into separate objects:

show-money: make object! [
    var: 1234.56
    bank: does [
        print ""
        print rejoin ["Your bank account balance is:  $" var]
        print ""
    ]
]

show-place: make object! [
    var: "Wabash"
    bank: does [
        print ""
        print rejoin [
            "Your favorite place is on the bank of the:  " var]
        print ""
    ]
]

show-money/bank 
show-place/bank

The "bank" function word and the "var" variable word now both refer to completely different things in their respective object contexts, but work correctly when accessed via the object paths. The "bank" function can make use of the "var" variable in a way that's properly related to its context. Wrapping code in objects in that way provides a simple method of organizing, grouping together, and maintaining local variables and functions that shouldn't be altered by other parts of a program.

To illustrate how the objects above could be cloned and extended, the objects below create GUIs which alter the contents of variables and make use of functions contained in the above objects:

deposit: make show-money [
    view layout [
        button "Deposit $10" [
            var: var + 10
            bank
        ]
    ]
]

travel: make show-place [
    view layout [
        new-favorite: field 300 trim {
            Type a new favorite river here, and press [Enter]} [
            var: value
            bank
        ]
    ]
]

As a derived object, the "deposit" object automatically has access to all the data and functions contained in the show-money object, and the "travel" object gets full access to the contents of the show-place object. In each new object, the action block of the GUI widget alters the local "var" variable and performs the local "bank" action, each inherited from the objects created earlier.

As you explore REBOL coding more deeply, you'll find that much of the language is built on object constructs. Working with graphics often requires extensive manipulation of objects. The name of the language itself stands for "Relative Expression Based Object Language". With that said, object oriented concepts aren't as fundamentally important in REBOL as they are in other languages. The REBOL "way" is to organize data and functions using blocks, and to refer to blocks with words. The fact that any block can automatically be treated as an ordinal series and manipulated using built-in list manipulation features is a fundamental design concept in REBOL. Another one of the primary differences that separates REBOL from other fundamentally object oriented languages is that mini language dialects can be built on word definitions, to provide a natural interface to data and function groupings. Those constructs provide an efficient, light weight, and powerful way to design programs, and they're among the main reasons that REBOL is a pleasure to use. Objects simply provide the additional ability to encapsulate and replicate code and to extend the built-in object constructs that already exist in the language.

For more information about how to put REBOL objects to use, see:

http://rebol.com/docs/core23/rebolcore-10.html

http://en.wikibooks.org/wiki/REBOL_Programming/Language_Features/Objects

http://en.wikipedia.org/wiki/Prototype-based_programming

30. Common Errors

Listed below are solutions to a variety of common errors you'll run into when first experimenting with Rebol:

1) "** Syntax Error: Script is missing a REBOL header" - Whenever you "do" a script that's saved as a file, it must contain at least a minimum required header at the top of the code. Just include the following text at the beginning of the script:

REBOL []

2) "** Syntax Error: Missing ] at end-of-script" - You'll get this error if you don't put a closing bracket at the end of a block. You'll see a similar error for unclosed strings. The code below will give you an error, because it's missing a "]" at the end of the block:

fruits: ["apple" "orange" "pear" "grape"
print fruits

Instead it should be:

fruits: ["apple" "orange" "pear" "grape"]
print fruits

Indenting blocks helps to find and eliminate these kinds of errors.

3) "** Script Error: request expected str argument of type: string block object none" - This type of error occurs when you try to pass the wrong type of value to a function. The code below will give you an error, because REBOL automatically interprets the website variable as a url, and the "alert" function requires a string value:

website: http://rebol.com
alert website

The code below solves the problem by converting the url value to a string before passing it to the alert function:

website: to-string http://rebol.com
alert website

4) "** Script Error: word has no value" - Miss-spellings will elicit this type of error. You'll run into it any time you try to use a word that isn't defined (either natively in the REBOL interpreter, or by you, in previous code):

wrod: "Hello world"
print word

5) To break out of an endless loop, or to otherwise stop the execution of errant code, just hit the [Esc] on your keyboard. If an error occurs in a "view layout" block, and the GUI becomes unresponsive, type "unview" at the interpreter command line and the broken GUI will be closed.

6) IMPORTANT: Here's a quirk of REBOL that doesn't elicit an error, but which can cause confusing results, especially if you're familiar with other languages:

unexpected: [
    empty-variable: ""
    append empty-variable "*"
    print empty-variable
]

do unexpected
do unexpected
do unexpected

The line:

empty-variable: ""

doesn't re-initialize the variable to an empty state. Instead, every time the block is run, "empty-variable" contains the previous value. In order to set the variable back to empty, as intended, use the word "copy" as follows:

expected: [
    empty-variable: copy ""
    append empty-variable "*"
    print empty-variable
]

do expected
do expected
do expected

7) Load/Save, Read/Write, Mold, Reform, etc. - another point of confusion you may run into initially with REBOL has to do with various words that read, write, and format data. When saving data to a file on your hard drive, for example, you can use either of the words "save" or "write". "Save" is used to store data in a format more directly usable by REBOL. "Write" saves data in a raw, 'unRebolized' form. "Load" and "read" share a comparable relationship. "Load" reads data in a way that is more automatically understood and put to use in REBOL code. "Read" opens data in exactly the format it's saved, byte for byte. Generally, data that is "save"d should also be "load"ed, and data that's "write"ed should be "read". For more information, see the following REBOL dictionary entries:

http://rebol.com/docs/words/wload.html

http://rebol.com/docs/words/wsave.html

http://rebol.com/docs/words/wread.html

http://rebol.com/docs/words/wwrite.html

Other built-in words such as "mold" and "reform" help you deal with text in ways that are either more human-readable or more natively readable by the REBOL interpreter. For a helpful explanation, see http://www.rebol.net/cookbook/recipes/0015.html.

8) Order of precedence - REBOL expressions are always evaluated from left to right, regardless of the operations involved. If you want specific mathematical operators to be evaluated first, they should either be enclosed in parenthesis or put first in the expression. For example, to the REBOL interpreter:

2 + 4 * 6

is the same as:

(2 + 4) * 6  ; the left side is evaluated first

== 6 * 6

== 36

This is contrary to other familiar evaluation rules. In many languages, for example, multiplication is typically handled before addition. So, the same expression:

2 + 4 * 6

is treated as:

2 + (4 * 6)  ; the multiplication operator is evaluated first

== 2 + 24

== 26

In REBOL, the left-right order of precedence is consistent, easy to remember, and holds true for all evaluated expressions. Line terminators are not even required, as in other languages (i.e., semicolons in C-like languages), so the following example:

save %text.txt read http://rebol.com print sort read %text.txt

may be written a bit more intuitively like this:

save %text.txt (read http://rebol.com) 
print (sort (read %text.txt))

That whole script is evaluated as follows: The "save" function accepts two parameters - first a filename, then the data to be written. That data is read from http://rebol.com (the next expression in order). Next in line, the "print" function takes one parameter. That data is the sorted text read from the text.txt file. The whole thing is simply evaluated left to right. Becoming accustomed to this thought process can be the source of some initial errors if you're already used to other languages.

30.1 Trapping Errors

There are several simple ways to keep your program from crashing when an error occurs. The words "error?" and "try" together provide a way to check for and handle expected error situations. For example, if no Internet connection is available, the code below will crash abruptly with an error:

html: read http://rebol.com

The adjusted code below will handle the error more gracefully:

if error? try [html: read http://rebol.com] [
    alert "Unavailable."
]

The word "attempt" is an alternative to the "error? try" routine. It returns the evaluated contents of a given block if it succeeds. Otherwise it returns "none":

if not attempt [html: read http://rebol.com] [
    alert "Unavailable."
]

To clarify, "error? try [block]" evaluates to true if the block produces an error, and "attempt [block]" evaluates to false if the block produces an error.

For a complete explanation of REBOL error codes, see: http://www.rebol.com/docs/core23/rebolcore-17.html.

31. How to Organize Your Coding Thought - Some Real World Examples

If you're just beginning to write code, it's easy to read through language documentation, understand all the constructs and examples, and still walk away saying to yourself "that's fine ... but, how do I write a program that does ______". Trying to tackle any specific programming situation from scratch requires not only an understanding of how language components work and how they're put together, but how to think in terms of organizing those components to materialize a working program from your imagined design. Until you've done that many times, it can be difficult to shape a program from raw code. This section is intended to provide some understanding of how you can approach organizing your thoughts and write code to satisfy your specific needs in any situation. Several short case studies are presented to provide insight as to how specific real life situations were approached.

31.1 Using Outlines and Pseudo Code

A great way to approach any coding project is to think through a flow chart of activity and to write out all the necessary coding requirements in a design outline consisting of natural language. Using spoken English "pseudo code" provides a structure for the detailed code you'll write. When approaching any coding project, start with a top-down approach. Go from general ideas about how the program will flow, to outline structure, to pseudo code within that structure, to final detailed code. As a last step, you'll typically debug the code and add/change functionality as you test and use the program.

To start out, explain to yourself what you want your intended program to do, and think through what the program must do internally to accomplish that goal. As you begin to make an imagined program take shape, think of how you expect the user and the computer to interact when the program is complete, and put those actions into words. Write down your explanation and flesh out the details of the imaginary program as much as possible. That will give you a starting point to begin tearing apart every requirement and organizing a plan of how the code will be structured to make each of those requirements flow from one to another.

To begin a structured outline, start with an idea of what data the user will input and what the computer will output. Every program needs a way to input and/or return data - GUI, command line interface, web page CGI input, etc. Most desktop applications use a GUI to interact with the user, but sometimes it's easier to think through the development process using console interactions. It's also easier to develop a CGI application if you've got a working console version. Whatever your conceived interface, remember that program flow is primarily linear. The majority of code you write will flow from one data definition or action to the next. You can begin the process of writing an outline by thinking of how the program must begin, and what must be done before the user starts to interact with the application. Think of any data or actions that need to be defined before the program starts. Then think of what must happen to accommodate each possible interaction the user might choose. This will help you define an interface, and it will help you begin to think in terms of actual bits of code that need to be written. In some cases, all possible actions may occur as a result of the user clicking various GUI widgets. That should elicit the thought of certain bits of GUI code structure, and you can begin writing an outline to design a GUI interface. If you imagine an online CGI application or a command line program, the user may respond to text questions or work with forms on a web page. Again, some code from the example applications in this tutorial should come to mind, and you can begin to form a coding structure that enables the general user interface.

To flesh out the program outline, you can use natural language pseudo code. For example, if you imagine a button in a GUI interface doing something for your user, you don't need to immediately write the REBOL code that the button runs. Initially, just write a description of what you want the button to do. The same is true for functions and other chunks of code. As you flesh out the outline more, you can describe the language elements and coding thought you conceive to perform various actions or to represent various data structures. As you outline a program to enable user interaction with the computer, don't get lost in the nitty gritty syntax details of writing actual code. It's easy to lose sight of the big picture at this stage. Instead, concentrate on the organizational scheme that provides an overview of how the program will operate. Most actions in a program will occur as a result of conditional evaluations (if this happens, do this...), loops, or linear flow from one action to the next. If you're going to perform certain actions multiple times or cycle through lists of data, you'll likely need to run through some loops. If you'll need to work with changeable data, you'll need to define some variable words, and you'll probably need to pass them to functions to process the data. Think in those general terms first. Create a list of data and functions that are required, and put them into an order that makes the program structure build and flow from one definition, condition, loop, GUI element, action, etc., to the next. If you spend time organizing your needs in terms of a natural language outline, and then fill that outline with pseudo code descriptions of code constructs, you'll have a much easier time converting those needs to actual code.

Instead of providing more vague and generalized notions about how to organize programming thought, what follows are a few case studies that describe how I've approached little programming tasks in a productive way. Each example traces my train of thought from the organizational process through the completed code.

31.2 Case 1 - Scheduling Teachers

In organizing my music lesson business, I needed a way for teachers to see their current weekly schedules. I needed them to see any additions/changes I'd made (new students, reschedulings, etc.), and I also needed to know what times they had available when scheduling new students. On top of that, I wanted the teachers to be able to make changes to their schedules, so that we were all in sync with current daily appointments. In other studios, I had typically seen this done with a weekly paper schedule. Each teacher had one piece of paper for each day's recurring weekly schedule. The available times were listed for each day, and when management added a new student, they wrote all the student info (name, parent name, phone number, etc.) onto the line for that weekly appointment time in the teacher's schedule. This was awkward for everyone. Teachers had to keep a list of rescheduled appointments in their daily calendars, and they couldn't possibly let management know about all their cancellations, rescheduled appointments, etc. That made for missed appointment opportunities, awkward double bookings, and lots of scribbling on the schedule. The teachers also had to come to the studio or be in touch with management by phone to be notified of schedule additions and changes.

A web based solution seemed like a perfect alternative to the weekly paper schedule. Teachers could check their schedules at home each day, and we'd all be able to see and adjust to current changes. At first, I imagined a database solution that saved changes to an online database, which could be accessed by a CGI front end. As I thought about it more, though, I began to wonder if it'd be better to simply create a small schedule page and use a content editor to manage the text on that page. That would provide a free form solution that mimicked the paper schedule teachers were familiar with, and eliminated the problems related to having paper schedules at one centralized location. To organize my thoughts, I created a text file in the same format as the paper schedules I'd seen:

Monday:

3     student1, 555-1234, parent's names, payment history, notes
3:30  student2, 555-1234, parent's names, payment history, notes
4     (gone 3-17)  student3, 555-1234, payment history, notes
4:30  student4, 555-1234, parent's names, payment history, notes
5     student5, 555-1234, parent's names, payment history, notes


Tuesday:

3     ----
3:30  ----
4     (john doe 3-18) ----
4:30  ----
5     student1, 555-1234, parent's names, payment history, notes
5:30  student2, 555-1234, parent's names, payment history, notes
6     student3, 555-1234, parent's names, payment history, notes
6:30  ----
7     student4, 555-1234, parent's names, payment history, notes
7:30  ----
8     student5, 555-1234, parent's names, payment history, notes
.
.
.

At a glance, the entire weekly schedule was visible. I used "----" to indicate an available appointment time, and any temporary changes to the weekly schedule could be noted in parentheses. This is how the teachers were used to working with their schedules.

I wanted to create the above schedule format on a web page, and I wanted it framed in an html document that had some permanent info that the teacher couldn't alter. I also wanted the teacher to be able to make adjustments without having to mess with ftp or anything having to do with the web site. They just needed to click an icon, type some changes into their schedule, and have it appear on the web page. Here's an outline of what I needed to accomplish:

  1. Download the current schedule text file.
  2. Backup a copy of the existing schedule, just in case.
  3. Edit the schedule.
  4. Upload the altered schedule data back to the website.
  5. Include the new schedule text in an html template, retaining the proper line format.
  6. Confirm that the changes were made correctly and that they displayed correctly on the web page.
  7. Keep the teacher interface simple and intuitive, like writing on a piece of paper.

After looking at the above outline, the best solution turned out to be the simplest. Instead of designing a database scheduling app with a complex GUI layout to manage all the data, I just did each step above in the most direct way possible in REBOL code:

; first set some initial variables:

url: http://website.com/teacher
ftp-url: ftp://user:pass@website.com/public_html/teacher

; ... and give the teacher some instructions: 

alert {Edit your schedule, then click save and quit.
    The website will be automatically updated.}

; 1) download the file containing the schedule text:

write %schedule.txt read join url ["/schedule.txt"]

; 2) create a timestamped backup on the web server:
write join ftp-url ["/" now/date "_" now/time] read %schedule.txt

; 3 and 7) edit the text:

editor %schedule.txt

; 4) save the edited text back to the web site:

write join ftp-url ["/schedule.txt"] read %schedule.txt

; 6) confirm that the changes are displayed correctly:

browse url

To satisfy step 5 in the outline, I created a downloadable executable (".exe" file) of the above program (using XpackerX), and in the http://website.com/teacher folder on the web site, I created an index.php file containing the following code:

<a href="./scheduler.exe" target=_blank>Download Scheduler</a>
<br><br><pre><?php include 'schedule.txt'; ?></pre>

The first line creates a download link, so that the teacher can download and run his scheduler program at any remote location. The second line includes the preformatted schedule text on the web page. I can put any other html I want on this page, which the teacher never touches.

What could have been a very long and involved programming task was accomplished in minutes, and has been in use every day for many months. The free form format enabled by using a simple text file provides the opportunity to incorporate various notes, changes, and info that would otherwise be awkward to include or difficult to emphasize in the weekly schedule. In this case, writing the outline/pseudo code provided an immediate solution, and it's worked out to be the best way to satisfy our needs.

31.3 Case 2 - Downloading Directories

Here's a situation that involved a bit more code design. I had a notebook computer that was disabled by a severe adware breakout. In an attempt to erase the troublesome files, the machine was rendered unable to boot to the operating system. I needed to copy a large number of recent data files that had not yet been backed up. Several options which didn't involve writing code were available to get this kind of job done. I could've removed the hard drive and put it in another machine, and then copied the files directly from one hard drive to another. I didn't have a hardware connector to install the laptop drive, and I didn't feel like taking the machine apart. I could've tried to reinstall the OS, and send the files across the network to be backed up on another computer. Without a system disk immediately available to restore the operating system, that wasn't convenient. I could've also potentially used a stand-alone local file transfer application (the "laplink" type), but without any serial/parallel ports, and without any OS access to provide USB support, I didn't have an application which made that option possible. Instead, I happened to have a Knoppix CD with which I was able to boot the laptop (http://www.knoppix.org/ provides the complete Linux operating system on a single free CD - it doesn't require any hard drive or any installation to run). I booted the computer to Knoppix, it found my network, and I started the Aprelium web server (http://aprelium.com/) on the laptop. Tada! Using another computer on the network, I was able to access all my files through the web server. I had access to the files at that point, but since I had literally thousands of files in hundreds of directories on the laptop, I couldn't download each one manually. Instead, I wrote a little spidering application in REBOL that did the job instantly.

To create the program in natural language, I thought about the process I would go through, and how I would click through the directory structure if I were to manually download each file:

  1. Create a new destination folder on the client computer to hold the transferred files.
  2. Start in the current subdirectory on the laptop (starting with the folder that held my data), and download all the files in it to the new destination directory on the client computer.
  3. Create subdirectories in the destination directory on the client to mirror each folder in the current directory on the laptop.
  4. Switch into each of the subdirectories on the laptop and on the client, and repeat steps 2-4 for each subdirectory.

I came up with the outline above by actually sitting down at the computer, and running though the process that I wanted to automate. I just took note of how the thought process was organized. Next, I converted the above ideas to pseudo-code, or something that described how I would accomplish the above things using code constructs:

  1. Get an initial remote url from the user. Use the built-in "request-text" function to do that. Then, create a local folder to mirror it, with a nicely formed name (only allowable Windows file name characters). Use the built-in "replace" function to swap out unusable characters, and the built-in "make-dir" function to create a destination folder with the cleaned up characters.
  2. Since the file and directory listings are made available via a web server, I'm going to have to parse a web page for file names to download. That's easy - the web server puts "/" characters at the end of all folder listings, so anything without a "/" at the end is a file. Create a block of file names, and use a "foreach" loop to go through the list of files, using read/binary and write/binary functions to download the actual files to the destination folder.
  3. I'll also need to parse the web page for folder names to create. Use another "foreach" loop to work through the block of folder names, and the "make-dir" function to create local directories with those names.
  4. Create a function that changes directories on both the local and remote machines. In order to work with the correct folders, I'll need to create some variables to keep track of the directory I started in, the current local folder I'm writing to, and the current remote folder I'm reading. As I switch in and out of each directory, I'll use rejoin and replace functions to concatenate and remove the current folder names to and from the local directory and remote url variables. Because I need to create a function that repeats both previous steps and THIS CURRENT step in every subdirectory, I'll need to enclose all three of those steps into a function, and call that function from within itself. The process of creating a function that calls itself is called "recursion" - it's needed here to do the same thing in every folder, drilling down until there are no more subfolders.

The first step was straightforward. Here's the code I came up with:

; Get initial remote url and create a local folder to mirror
; it, with a nicely formed name (only allowable Windows file
; name characters).
initial-pageurl: to-url request-text/default trim {
    http://192.168.1.4:8001/4/}
initial-local-dir: copy initial-pageurl
replace initial-local-dir "http://" ""
replace/all initial-local-dir "/" "_"
replace/all initial-local-dir "\" "__"
replace/all initial-local-dir ":" "--"
lrf: to-file rejoin [initial-local-dir "/"]
if not exists? lrf [make-dir lrf]
change-dir lrf
clf: lrf

Since steps 2-4 above would all be enclosed in a single function, I decided I should assign some variable words that would refer to the folders I'd be accessing: "lrf" = local-root-folder, "clf" = current-local-folder and "crfu" = current-remote-folder-url.

To begin step 2, I wrote a bit of code to do the parsing of the file and folder names on the current web page directory listing. I combined the parsing requirements from step 2 and 3 above, and decided to use the variable words "files" and "folders" to label the blocks that would contain the parsed results. Here's the code that I came up with to read and parse the contents of the current page into the usable blocks. It looks for any link (anything beginning with href=" and ending with "), and appends it to the folders block if it contains a "/" character. Anything that doesn't contain the "/" character gets appended to the file block:

page-data: read crfu
files: copy []
folders: copy []
parse page-data [
    any [
        thru {href="} copy temp to {"} (
            last-char: to-string last to-string temp
            either last-char = "/" [
                ; don't go upwards through the folder structure:
                if not temp = "../" [
                    append folders temp
                ]
            ][
                append files temp
            ]
        )
    ] to end
]

To complete step 2, here's the foreach loop that I came up with to download all the files contained in the file block. It contains a replace/rejoin trick to make sure the filename gets concatenated to the current url correctly (with no extra "/"s):

foreach file files [
    print rejoin ["Getting: " file]
    new-page: rejoin [crfu "//" file]
    replace new-page "///" "/"
    write/binary to-file file read/binary to-url new-page
]

I ran into some problems with certain links on the web page that weren't actually file or folder listings, or which didn't download properly. I used some conditional "if"s and "error? try" combinations to eliminate those problems. I wrote the errors to a text file, so that I could check them afterwards and download manually if necessary. Here's the revised version of the code above, with the error handling routines:

foreach file files [
    if not file = "http://www.aprelium.com" [
    ; The free aprelium server puts that link on all pages
    ; it serves.  I didn't want to spider all the contents of
    ; their web page.
        print rejoin ["Getting: " file]
        new-page: rejoin [crfu "//" file]
        replace new-page "///" "/"
        if not exists? to-file file [
            either error? try [read/binary to-url new-page] [
                write/append %/c/errors.txt rejoin [
                    "There was an error reading:  " new-page
                    newline]
            ] [
            if error? try [
            write/binary to-file file read/binary to-url new-page
            ][
                write/append %/c/errors.txt rejoin [
                "error writing: " crfu newline]]
            ]
        ]
    ]
]

I wanted to complete step 3, but realized that that's where the recursion pattern needed to occur - for each folder I copied, I wanted to look inside that folder and create any folders it contained, and then inside those folders, etc. So next, I defined a recursion pattern to change into the current local and remote folders, and to run the function in which all of steps 2-4 were contained. I decided to label the entire enclosing function "copy-current-dir" - it would be passed the parameters "lrf", "clf", and "crfu". That function contains the recurse function, which calls the encompassing copy-current-dir function, which itself contains the recurse function, etc. The effect of this recursion is that every subfolder of every folder is entered. Here's the recurse function:

recurse: func [folder-name] [
    change-dir to-file folder-name
    crfu: rejoin [crfu folder-name]
    clf: rejoin [clf folder-name]
    ; NOW HERE'S THE RECURSION - call the function in which
    ; this function is contained:
    copy-current-dir crfu clf lrf
    ;  When done, go back up a folder on both the local and
    ;  remote machines.  The replace actions remove the current
    ;  folder text from the end of the current folder strings.
    change-dir %..
    replace clf folder-name ""
    replace crfu folder-name ""
]

Finally, I completed steps 3 and 4 by creating local folders to mirror each directory in the current remote folder, and then called the recurse function to spider down through them. I used a foreach loop to work through each directory in the current subdirectory list. Because this loop contains the recurse function, which in turn runs the copy-current-dir, which in turn contains this loop, every subdirectory of every subdirectory is worked through, until the job is complete:

foreach folder-name folders [
    make-dir to-file folder-name
    recurse folder-name
]

I wrapped the parsing, looping/reading, and recursing sections into the copy-current-dir function so that they could be called recursively. Then I added some error handling routines as I played with the working code. I included a block of urls to be avoided, and some code in the final foreach loop to check that those urls weren't already downloaded (in case I had previously run the program on the same directory). Here's the final script:

REBOL [title: "Directory Downloader"]

avoid-urls: [
    "/4/DownLoad/en_wikibooks_org/skins-1_5/common/&"
    "DownLoad/groups_yahoo_com/group/Join%20This%20Group!/"
    "DownLoad/pythonide_stani_be/ads/"
    "Nick%20Antonaccio/Desktop/programming/api/ewe/"
]

copy-current-dir: func [
{
    Download the files from the current remote directory
    to the current local directory and create local subfolders
    for each remote subfolder.  Then recursively do the same
    thing inside each sub-folder.
} 
    crfu  ; current-remote-folder-url
    clf   ; current-local-folder
    lrf   ; local-root-folder
] [
    ; Check that the url about to be parsed is not in the avoid
    ; list above.  This provides a way to skip specified folders
    ; if needed:

    foreach avoid-url avoid-urls [
        if find crfu avoid-url [return "avoid"]
    ]

    ; First, parse the remote folder for file and folder names.
    ; Given the url of a remote page, create 2 list variables.
    ; files:  remote files to download (in current directory)
    ; folders:  remote sub-directories to recurse through.
    ; There's an error check in case the page can't be read:

    if error? try [page-data: read crfu] [
        write/append %/c/errors.txt rejoin [
            "error reading (target read error): " 
            crfu newline]
        return "index.html"
    ]

    ; if the web server finds an index.html file in the folder
    ; it will serve its contents, rather than displaying the 
    ; directory structure.  Then it'll try to spider the html
    ; page.  The following will keep that error from occuring.
    ; NOTE:  this error was more effectively stopped by 
    ; editing the index page names in the Abyss web server:
    if not find page-data {Powered by <b><i>Abyss Web Server} [
        ; </i></b>
        write/append %/c/errors.txt rejoin [
            "error reading (.html read error): " 
            crfu newline]
        return "index.html"
    ]
    files: copy []
    folders: copy []
    parse page-data [
        any [
            thru {href="} copy temp to {"} (
                last-char: to-string last to-string temp
                either last-char = "/" [
                ; don't go upwards through the folder structure:
                    if not temp = "../" [
                        append folders temp
                    ]
                ][
                    append files temp
                ]
            )
        ] to end
    ]

    ; Next, download the files in the current remote folder
    ; to the current local folder:

    foreach file files [
        if not file = "http://www.aprelium.com" [
            print rejoin ["Getting: " file]
            new-page: rejoin [crfu "//" file]
            replace new-page "///" "/"
            if not exists? to-file file [
                either error? try [read/binary to-url new-page][
                    write/append %/c/errors.txt rejoin [
                        "There was an error reading:  " new-page
                        newline]
                ] [
                if error? try [
        write/binary to-file file read/binary to-url new-page
                ][
                    write/append %/c/errors.txt rejoin [
                    "error writing: " 
                    crfu newline]]
                ]
            ]
        ]
    ]

    ; Check to see if there are no more subfolders.  If so,
    ; exit the copy-current-dir function

    if folders = [] [return none]

    ; Define the recursion pattern.  This changes into the 
    ; current local folder, and runs the copy-current-dir 
    ; function (the current function we are in), which itself
    ; contains the recurse function, which itself will call
    ; the copy-current-dir, etc.  The effect of this recursion
    ; is that every subfolder of every folder is entered.  
    ; This is what enables the spidering:

    recurse: func [folder-name] [
        change-dir to-file folder-name
        crfu: rejoin [crfu
            folder-name]
        clf: rejoin [clf
            folder-name]
        copy-current-dir crfu clf lrf
        ;  When done, go back up a folder on both the local
        ;  and remote machines.  The replace actions remove
        ;  the current folder text from the end of the current
        ;  folder strings.
        change-dir %..
        replace clf folder-name ""
        replace crfu folder-name ""
    ]

    ; Third, create local folders to mirror each directory in
    ; the current remote folder, and then spider down through
    ; them using the recurse function to download all the files
    ; and subdirectories included in each folder:

    foreach folder-name folders [
    ;   foreach avoid-url avoid-urls [
    ;       if not find folder-name avoid-url [
                make-dir to-file folder-name
                recurse folder-name
    ;       ]
    ;   ]
    ]
]

; Now, get initial remote url and create a local folder to
; mirror it, with a nicely formed name (only allowable Windows
; file name characters).

    initial-pageurl: to-url request-text/default trim {
        http://192.168.1.4:8001/4/}
    initial-local-dir: copy initial-pageurl
    replace initial-local-dir "http://" ""
    replace/all initial-local-dir "/" "_"
    replace/all initial-local-dir "\" "__"
    replace/all initial-local-dir ":" "--"
    lrf: to-file rejoin [initial-local-dir "/"]
    if not exists? lrf [make-dir lrf]
    change-dir lrf
    clf: lrf

; Start the process by running the copy-current-dir function:

copy-current-dir initial-pageurl clf lrf

print "DONE" halt

31.4 Case 3 - Creating a Simple Calculator Application

GUI calculator examples are cliche among computer programming tutorials, so I decided to include a quick case study about how I built a small calculator application in REBOL. Compare this example to those created in other programming languages, and you'll get a sense of just how much simpler it is to write REBOL code.

Since this program will be primarily an exercise in simple GUI layout, I started with a pseudo-code outline of how I wanted the program to look when complete:

  1. There needs to be a display area to show numerical digits as they were input, as well as the results of calculations. A simple GUI text field will work fine for that display.
  2. There need to be GUI buttons to enter numerical digits and a decimal point, as well as buttons for mathematical operators, and a button to execute calculations (an "=" sign). Each of those three categories of buttons will do generally the same types of actions, so I'll create them as separate GUI styles, each with shared action blocks.

That was enough of an outline to begin writing some actual REBOL GUI code. I toyed with various window, button, and font sizes/colors until the layout looked acceptable. Here's what I came up with using the pseudo-code above:

view center-face layout/tight [
    size 300x350 space 0x0 across ; basic window sizing/spacing
    display: field 300x50 font-size 28 "0" return ; the display
    style butn button 100x50 [
        ; add the action code here for number buttons
    ]
    style eval button 100x50 brown font-size 13 [
        ; add the action code here for operator buttons
    ]
    butn "1"  butn "2"  butn "3"  return   ; arrange those buttons
    butn "4"  butn "5"  butn "6"  return   ; in the window
    butn "7"  butn "8"  butn "9"  return 
    butn "0"  butn "."  eval "+" return
    eval "-" eval "*" eval "/" return
    button 300x50 gray font-size 16 "=" [
        ; add the action code here for "=" sign button
    ]
]

To turn the above display into a functioning calculator, next I needed to think about what must happen when the number buttons are clicked. Here's some pseudo-code to outline that thought process:

  1. The user must be able to enter numbers that are longer than a single digit, so every time a number button is clicked, that numerical digit should be appended to the digits in the display. I'll use "rejoin" to build the display number, and then I'll set a variable to store that number, each time a new digit is clicked.
  2. In the GUI code above, I started with a "0" in the display field. That'll need to be erased before any other numbers are displayed.

That's all easy enough to do in REBOL code:

if display/text = "0" [display/text: ""]  ; erase the displayed "0"
display/text: rejoin [display/text value]  ; build the displayed #
show display
cur-val: display/text  ; use a variable to save the displayed #

Now I need to think about what should happen when the operator buttons are clicked:

  1. I need to assign a variable to save the number currently entered in the GUI display (that number is already saved temporarily in the "cur-val" variable above).
  2. Erase the display to prepare for a new number to be entered.
  3. Assign a variable to save the operator selected.

That's all very simple - in fact, it's simpler in real REBOL code than it is in pseudo-code:

prev-val: cur-val  ; save the displayed # in a variable
display/text: "" show display  ; erase the display
cur-eval: value  ; save the selected operator in a variable

Finally, I need to think about what happens when the "=" button is clicked:

  1. A computation must be evaluated, using the first number entered (the "prev-val" variable above), the operator entered (the "cur-eval" variable), and the second number entered ("cur-val").
  2. The display area needs to be updated to show the value of that computation.

The easiest way I could think to build the computation was to use the "rejoin" function to build a string representing the first number entered, the operator entered, and the second number entered. I could then evaluate that computation by simply using the "do" function on the built string:

cur-val: do rejoin [prev-val " " cur-eval " " cur-val]
display/text: cur-val
show display

That was all very easy. Here's the code we've got so far:

view center-face layout/tight [
    size 300x350 space 0x0 across
    display: field 300x50 font-size 28 "0" return
    style butn button 100x50 [
        if display/text = "0" [display/text: ""]  ; erase the "0"
        display/text: rejoin [display/text value]  ; build the #
        show display
        cur-val: display/text  ; use a variable to save the displayed #
    ]
    style eval button 100x50 brown font-size 13 [
        prev-val: cur-val
        display/text: "" show display
        cur-eval: value
    ]
    butn "1"  butn "2"  butn "3"  return
    butn "4"  butn "5"  butn "6"  return
    butn "7"  butn "8"  butn "9"  return 
    butn "0"  butn "."  eval "+" return
    eval "-" eval "*" eval "/" return
    button 300x50 gray font-size 16 "=" [
        cur-val: do rejoin [prev-val " " cur-eval " " cur-val]
        display/text: cur-val
        show display
    ]
]

After testing the code a bit, I found a bug. Whenever the first computation is completed, any additional digits entered are appended to the total displayed from the first calculation. That happens in this line of code in the number buttons definition (the "butn" style):

display/text: rejoin [display/text value]

That problem is easily solved by setting a flag variable when the "=" button is clicked:

display-flag: true

. . . and then checking for that flag every time a number button is clicked - if the flag is set (meaning that a total is being displayed), erase the display so that a new number can be entered, and reset the flag variable:

if display-flag = true [display/text: "" display-flag: false]

That fixes the first bug. Testing the program a little more, I found another small bug. The calculator would crash with an error if the "=" sign or any of the operator buttons were clicked before numerical digits were properly entered. That was easy to fix by simply setting some default variables in the beginning of the program - that's a fundamentally good practice in any sort of programming:

prev-val: cur-val: 0 cur-eval: "+" display-flag: false

After using the program a bit more, I found another bug. If the equal sign was clicked repeatedly, it would perform calculations that weren't intended. The following line was the culprit:

cur-val: do rejoin [prev-val " " cur-eval " " cur-val]

The "cur-val" variable was updated every time the "=" button was clicked, whether or not a new number or operator was entered. To squash that bug, I just used the "display-flag" variable that was created earlier to check if a total was being displayed. I wrapped all of the action code performed when the "=" sign was clicked, into an "if" conditional, and only performed those actions if the flag had been reset (only if a total was not being displayed):

if display-flag <> true [ ... ]

Finally, there was a bug I'd had in mind from the beginning: if the user tried to divide by 0, the program would crash. To handle this situation, I added the following conditional check inside the code above:

if ((cur-eval = "/") and (cur-val = "0")) [
    alert "Division by 0 is not allowed." break
]

At this point, the program appeared to be reasonably bug free, so I decided to add an additional feature that seemed useful while testing the code. I wanted a running printout of all calculations performed, similar to paper tape on traditional printing calculators. Adding that feature was as simple as could be. At the beginning of the program I added a "print 0" line, and then added the following line changes to the "=" button:

prin rejoin [prev-val " " cur-eval " " cur-val " = "]
print display/text: cur-val: do rejoin [
    prev-val " " cur-eval " " cur-val
]

Here's the final calculator program:

REBOL [title: "Calculator"]

prev-val: cur-val: 0 cur-eval: "+" display-flag: false
print "0"
view center-face layout/tight [
    size 300x350 space 0x0 across
    display: field 300x50 font-size 28 "0" return
    style butn button 100x50  [
        if display-flag = true [display/text: "" display-flag: false]
        if display/text = "0" [display/text: ""]
        display/text: rejoin [display/text value] 
        show display
        cur-val: display/text
    ]
    style eval button 100x50 brown font-size 13 [
        prev-val: cur-val
        display/text: "" show display
        cur-eval: value
    ]
    butn "1"  butn "2"  butn "3"  return
    butn "4"  butn "5"  butn "6"  return
    butn "7"  butn "8"  butn "9"  return 
    butn "0"  butn "."  eval "+" return
    eval "-" eval "*" eval "/" return
    button 300x50 gray font-size 16 "=" [
        if display-flag <> true [ 
            if ((cur-eval = "/") and (cur-val = "0")) [
                alert "Division by 0 is not allowed." break
            ]
            prin rejoin [prev-val " " cur-eval " " cur-val " = "]
            print display/text: cur-val: do rejoin [
                prev-val " " cur-eval " " cur-val
            ]
            show display
            display-flag: true
        ]
    ]
]

31.5 Case 4 - Creating a Tetris Game Clone

One of my favorite games to play is Tetris. I particularly like the "Rebtris" clone, written in REBOL by Frank Sievertsen. While playing Rebtris recently, it struck me that writing a Tetris clone of my own would be a helpful exercise for this tutorial. In conceiving how to make it a bit different from all the other endless variations of Tetris, I thought "why not try a text version?". Creating it may be easier than a GUI version, and it would run on machines where GUI versions of REBOL aren't available. Writing a text version of Tetris would also force me to organize a set of display techniques that could be useful in laying out other text-based applications. Sounds like a fun project with some useful side effects.

NOTE: I've never considered how to build a Tetris clone, and I'm writing this section as I design, experiment, and write code for the program. So as you read this, you'll follow my exact train of thought in putting the program together, and I'll make no attempt to artificially clean up the process. The point of this tutorial is to demonstrate exactly how to go about creating all types of programs on your own. I hope that following my footsteps exactly - imperfections and all - should be a helpful experience. Let's see what happens...

Instead of starting this entire thing from scratch, I remembered briefly skimming a tutorial about how to create a text positioning dialect called "TUI". I found it again at http://www.rebolforces.com/articles/tui-dialect/ and looked for some reusable code...

Here's the TUI dialect, created by Ingo Hohmann (I renamed his "cursor2" function to "tui"):

tui: func [
    {Cursor positioning dialect (iho)}
    [catch]
    commands [block!]
    /local screen-size string arg cnt cmd c err
][
    screen-size: (
        c: open/binary/no-wait [scheme: 'console]
        prin "^(1B)[7n"
        arg: next next to-string copy c
        close c
        arg: parse/all arg ";R"
        forall arg [change arg to-integer first arg]
        arg: to-pair head arg
    )
    string: copy ""
    cmd: func [s][join "^(1B)[" s]
    if error? set/any 'err try [
        commands: compose bind commands 'screen-size ][
        throw err
    ]
    arg: parse commands [
        any [
            'direct set arg string! (append string arg) |
            'home  (append string cmd "H") |
            'kill  (append string cmd "K") |
            'clear (append string cmd "J") |
            'up    set arg integer! (append string cmd [
                arg "A"]) |
            'down  set arg integer! (append string cmd [
                arg "B"]) |
            'right set arg integer! (append string cmd [
                arg "C"]) |
            'left  set arg integer! (append string cmd [
                arg "D"]) |
            'at   set arg pair! (append string cmd [
                arg/x ";" arg/y "H" ]) |
            'del   set arg integer! (append string cmd [
                arg "P"]) |
            'space set arg integer! (append string cmd [
                arg "@"]) |
            'move  set arg pair! (append string cmd [
                arg/x ";" arg/y "H" ]) |
            set cnt integer! set arg string! (
                append string head insert/dup copy "" arg cnt
            ) |
            set arg string! (append string arg)
        ]
        end
    ]
    if not arg [throw make error! "Unable to parse block"]
    string
]

I read the tutorial and played with the code a bit. In addition to being a great tutorial, the end product is a useful tool for formatting output in console applications. The function "tui" gets passed a block of parameters including text to be printed, directional keywords to move the cursor around the screen ("home", "up", "down", "left", "right", and "at" a specific location) and several other commands to get the screen size, clear the screen, delete text, repeat text, and insert spaces.

I tried a few commands to get familiar with the syntax:

prin tui [ clear ]
print tui [ 50  "-" ]
print tui [ right 10 down 7 50  "x" ]
prin tui [ clear right 10 down 10 50  "x" ]
print tui [ clear home "message1"]
print tui [ home space 20 "message2"]
print tui [ at 20x20 "message3" kill "message4"]
print tui [ at 20x20  del 10]
print tui [ move 10x10]
prin tui [ clear (screen-size/y * screen-size/x - 4)  "x" ]

I had more fun playing with the TUI dialect than I did playing Tetris :) Basically, TUI wraps up some of the native print control codes built into REBOL, in a nice clean format that eliminates all the odd characters used in native codes. It contains everything required to move game pieces around the screen, so I'll start to come up with some requirements to build the game. Here's an outline that covers the main design goals I'm conceiving at this point:

  1. Draw a static playing field (the unchanging graphic backdrop design that's on screen the whole time the game is being played). This will represent the left, right, and lower vertical bounds which the game coordinates may not exceed.
  2. There are 7 block shapes used in the game. Create text versions of each graphic shape, in each of the 4 possible rotated positions. Come up with code to print and delete each of the graphic shapes. The TUI dialect will let me print and delete characters anywhere on the screen. I'll use directional statements to print the required characters, starting from any given coordinate. Put all these shape routines into a block for easy naming and reuse.
  3. Write a continuous loop to put one shape on the screen, make it fall at a given speed, and allow the user to spin it around and move it left-right.
  4. If a shape touches the bottom of the playing field, make it lock into the grid of other shapes that have already fallen. If the bottom row is complete, remove it, and make all the rows above it fall down a row to take its place. If the shape touches the ceiling, end the game.

The first part of the outline is easy. I'll use the "print a-line" code created in the "looping through data" example earlier in this tutorial. Here's a simple little backdrop that's printed to the screen:

a-line: copy [] loop 28 [append a-line " "] 
a-line: rejoin ["   |"  to-string a-line "|"]
loop 30 [print a-line] prin "   " loop 30 [prin "+"] print ""

For the second part, I need to create some code to print the 7 block shapes. They look like this:

;   1   ####    2   ###     3   ###     4   ###
;                    #          #             #
;
;   5   ##      6    ##     7   ##
;        ##         ##          ##

Here are all the possible variations when the above shapes are rotated clockwise:

;                   #
;   1   ####    2   #
;                   #
;                   #
;
;   3   ###     4    #      5    #      6   #
;        #          ##          ###         ##
;                    #                      #
;
;   7   ###     8   ##      9     #     10  #
;       #            #          ###         #
;                    #                      ##
;
;   11  ###     12  #       13  #       14  ##
;         #         #           ###         #
;                  ##                       #
;
;   15  ##      16   #
;        ##         ##
;                   #
;
;   17   ##     18  #
;       ##          ##
;                    #
;
;   19  ##
;       ##

To print any piece, I can start at the top left coordinate in the shape and move the appropriate number of spaces right, left, and/or down to print the other characters in each piece. Starting at the first character in shape 2, for example, I would move as follows: "#", down 1 left 1, "#", down 1 left 1, "#", down 1 left 1, "#". Shape 3 would move as follows: "###", down 1 left 2, "#". Here's a block called "shape", made up of individual blocks that can be passed to tui to print all the above shapes:

shape: [
    ["####"]
    ["#" down 1 left 1 "#" down 1 left 1 "#" down 1 left 1 "#"]
    ["###" down 1 left 2 "#"]
    [right 1 "#" down 1 left 2 "##" down 1 left 1 "#"]
    [right 1 "#" down 1 left 2 "###"]
    ["#" down 1 left 1 "##" down 1 left 2 "#"]
    ["###" down 1 left 3 "#"]
    ["##" down 1 left 1 "#" down 1 left 1 "#"]
    [right 2 "#" down 1 left 3 "###"]
    ["#" down 1 left 1 "#" down 1 left 1 "##"]
    ["###" down 1 left 1 "#"]
    [right 1 "#" down 1 left 1 "#" down 1 left 2 "##"]
    ["#" down 1 left 1 "###"]
    ["##" down 1 left 2 "#" down 1 left 1 "#"]
    ["##" down 1 left 1 "##"]
    [right 1 "#" down 1 left 2 "##" down 1 left 2 "#"]
    [right 1 "##" down 1 left 3 "##"]
    ["#" down 1 left 1 "##" down 1 left 1 "#"]
    ["##" down 1 left 2 "##"]
]

Now I can use the format "prin tui shape/number" to print any shape. For example:

prin tui shape/3

is the same as writing:

prin tui [right 1 "#" down 1 left 2 "##" down 1 left 1 "#"]

I came up with the following code to print out each shape, to check for errors, and to get used to using the above format. Notice the use of the "compose" function:

for i 1 19 1 [
    print tui [clear] 
    print rejoin ["shape " i ":"]
    do compose [print tui shape/(i)]
    ask ""
]

To erase the shapes, I decided to extend the block using duplicates of each shape to print spaces instead of "#"s. Now all I have to do is add 19 to any shape's index number, and I can print out a shape made of spaces that erases the original shape made of "#"s. Here's the final shape block:

shape: [
    ["####"]
    ["#" down 1 left 1 "#" down 1 left 1 "#" down 1 left 1 "#"]
    ["###" down 1 left 2 "#"]
    [right 1 "#" down 1 left 2 "##" down 1 left 1 "#"]
    [right 1 "#" down 1 left 2 "###"]
    ["#" down 1 left 1 "##" down 1 left 2 "#"]
    ["###" down 1 left 3 "#"]
    ["##" down 1 left 1 "#" down 1 left 1 "#"]
    [right 2 "#" down 1 left 3 "###"]
    ["#" down 1 left 1 "#" down 1 left 1 "##"]
    ["###" down 1 left 1 "#"]
    [right 1 "#" down 1 left 1 "#" down 1 left 2 "##"]
    ["#" down 1 left 1 "###"]
    ["##" down 1 left 2 "#" down 1 left 1 "#"]
    ["##" down 1 left 1 "##"]
    [right 1 "#" down 1 left 2 "##" down 1 left 2 "#"]
    [right 1 "##" down 1 left 3 "##"]
    ["#" down 1 left 1 "##" down 1 left 1 "#"]
    ["##" down 1 left 2 "##"]

    ; Here are the same shapes, with spaces instead of "#"s:

    ["    "]
    [" " down 1 left 1 " " down 1 left 1 " " down 1 left 1 " "]
    ["   " down 1 left 2 " "]
    [right 1 " " down 1 left 2 "  " down 1 left 1 " "]
    [right 1 " " down 1 left 2 "   "]
    [" " down 1 left 1 "  " down 1 left 2 " "]
    ["   " down 1 left 3 " "]
    ["  " down 1 left 1 " " down 1 left 1 " "]
    [right 2 " " down 1 left 3 "   "]
    [" " down 1 left 1 " " down 1 left 1 "  "]
    ["   " down 1 left 1 " "]
    [right 1 " " down 1 left 1 " " down 1 left 2 "  "]
    [" " down 1 left 1 "   "]
    ["  " down 1 left 2 " " down 1 left 1 " "]
    ["  " down 1 left 1 "  "]
    [right 1 " " down 1 left 2 "  " down 1 left 2 " "]
    [right 1 "  " down 1 left 3 "  "]
    [" " down 1 left 1 "  " down 1 left 1 " "]
    ["  " down 1 left 2 "  "]
]

I wrote another quick script to test it. Notice the "i + 19" used to erase the exiting shape:

for i 1 19 1 [
    print tui [clear] 
    print rejoin ["shape " i ":"]
    do compose [prin tui [move 10x10] print tui shape/(i)]
    ask ""
    do compose [prin tui [move 10x10] print tui shape/(i + 19)]
    print rejoin ["shape " i " has been erased."]
    ask ""
]

Beautiful. Steps 1 and 2 are complete. Now I can work on the last part of the outline (the things related to how the game actually plays, moving pieces and responding to user input). First, I'll get the shapes to fall down the screen. That'll be done by printing, erasing and then redrawing the piece one row lower, in a continuous loop. Here's an outline to organize that thought process:

  1. Start by clearing the screen.
  2. Pieces appear in a random order, so come up with a random number to represent some random shape's index number.
  3. Use a for loop to increment the vertical position of the piece: for each row, print the random piece number at the current horizontal position (initially set to 15), and at the vertical position represented by the current "for" variable.
  4. Wait a moment, then erase the piece (using the shape number + 19). Then increment the row number and start again.
  5. When the piece reaches the last row, print it there without erasing.
  6. Wrap that whole thing in a forever loop to keep it going indefinitely.

Let's get that much going:

prin tui [clear]

forever [
    random/seed now
    r: random 19    ; the number of a random shape
    xpos: 18        ; the initial horizontal position
    for i 1 25 1 [
        pos: to-pair rejoin [i "x" xpos]
        ; print the shape represented by "r" at the "pos"
        ; coordinate:
        do compose/deep [prin tui [at (pos)] print tui shape/(r)]
        ; The wait time could be a user controlled variable, or
        ; it could be sped up as the difficulty level increases:
        wait :00:00.30
        ; erase the shape, then continue the loop:
        do compose/deep [
            prin tui [at (pos)] print tui shape/(r + 19)]
    ]
    ; reprint the shape at its final resting place:
    do compose/deep [prin tui [move (pos)] print tui shape/(r)]
]

NOTE: It struck me in writing the above code that the TUI function actually takes all its coordinates in an unusual order. Typically, in coordinates with the form XxY, "X" is the horizontal position and "Y" is the vertical position. TUI uses the format YxX, where Y is the vertical position measured in rows from the top of the screen. X is the horizontal position, measured in columns from the left side of the screen. Keep in mind that the order of X and Y coordinates is opposite the normal expectation.

Now I need to come up with a way for the user to control the horizontal position of the shape. Here's some pseudo code to help me think about how to do that:

  1. Be on the lookout for keystroke input from the user.
  2. If the user presses the "l" key, add 1 to the current horizontal position of the shape (held in the variable "xpos"). If the user presses the "k" key, subtract 1 from xpos.

First, I need a way to get keystroke input without blocking the program flow (i.e., I need to wait for keystroke input to be acknowledged when it occurs, but I can't just stop the normal program flow to wait for key presses. For game play to continue, the "for" and "forever" loops can't be interrupted. So I searched Google for "REBOL key stroke" and got pointed to the following code at http://www.rebol.org/cgi-bin/cgiwrap/rebol/ml-display-thread.r?m=rmlSCRQ (in the REBOL mailing list archive):

c: open/binary/no-wait [scheme: 'console]
; set following to whatever you wish
; intentionally slow at 2 secs so you can "see" the effect
wait-duration: :0:2
d: 0
forever [
    if not none? wait/all [c wait-duration] [
        print to-char to-integer copy c
    ]
    d: d + 1 ;let's do other stuff
    print d
]

That little bit of code does exactly what I need. The parts required for my needs are:

c: open/binary/no-wait [scheme: 'console]
forever [
    if not none? wait/all [c wait-duration] [
        print to-char to-integer copy c
    ]
]

I adjusted the variable names, checked for "k" or "l" key presses, and used the code below to test that it worked the way I wanted:

keys: open/binary/no-wait [scheme: 'console]
forever [
    if not none? wait/all [keys :00:00.01] [
        switch to-string to-char to-integer copy keys [
            "k" [print "you pressed k"]
            "l" [print "you pressed l"]
        ]
    ]
; print "nothing pressed" ; make sure it's working
]

Next, I integrated the above code into the loop created earlier to drop the shape down the screen. Notice that I added a conditional "if", to be executed when either "k" or "l" keystrokes are encountered. It checks that the horizontal bounds don't go outside the 5-30 positions. That keeps the shapes within the horizontal boundaries of the playing field. Also, notice that the variable "old-xpos" is used to hold the position of the shape that needs to be erased:

keys: open/binary/no-wait [scheme: 'console]
forever [
    random/seed now
    r: random 19
    xpos: 18
    for i 1 25 1 [
        pos: to-pair rejoin [i "x" xpos]
        do compose/deep [prin tui [at (pos)] print tui shape/(r)]
        old-xpos: xpos 
        if not none? wait/all [keys :00:00.30] [
            switch to-string to-char to-integer copy keys [
                "k" [if (xpos > 5) [xpos: xpos - 1]]
                "l" [if (xpos < 30) [xpos: xpos + 1]]
            ]
        ]
        pos: to-pair rejoin [i "x" old-xpos]
        do compose/deep [
            prin tui [at (pos)] print tui shape/(r + 19)]
    ]
    do compose/deep [prin tui [move (pos)] print tui shape/(r)]
]

It's coming along well :) Now I need to be able to spin the shapes around. Here's some pseudo code to organize my thoughts:

  1. Watch for the "O" key to be pressed. That will be the keycode to run the shape spinning code.
  2. Create a set of conditionals to cycle through the list of rotated shapes related to the current shape. For example, if the current shape (variable "r") is number 12, then the rotated versions of that shape are numbers 11-14. With each press of the "O" key, replace the variable r with the next shape in that list. That logic must "wrap around" (i.e., the next shape after 14 should be 11). Instead of using a block list of shapes to do this, I decide to use a switch structure to individually map each shape to the one it should rotate to (something like "if shape r is now #14, turn shape r into #11" - do that explicitly for each shape).

I already have some code to watch for keystrokes, so I'll try the last part of the above outline first:

switch to-string r [
    "1" [r: 2]
    "2" [r: 1]
    "3" [r: 4]
    "4" [r: 5]
    "5" [r: 6]
    "6" [r: 3]
    "7" [r: 8]
    "8" [r: 9]
    "9" [r: 10]
    "10" [r: 7]
    "11" [r: 12]
    "12" [r: 13]
    "13" [r: 14]
    "14" [r: 11]
    "15" [r: 16]
    "16" [r: 15]
    "17" [r: 18]
    "18" [r: 17]
    "19" [r: 19]
]

Wait a sec - that makes the shapes rotate clockwise (from #11 go to #12, #14 to #11, etc.) I prefer for them to rotate counterclockwise (#11 to #14, #14 to #13, etc). Here's the revised code:

switch to-string r [
    "1" [r: 2]
    "2" [r: 1]
    "3" [r: 6]
    "4" [r: 3]
    "5" [r: 4]
    "6" [r: 5]
    "7" [r: 10]
    "8" [r: 7]
    "9" [r: 8]
    "10" [r: 9]
    "11" [r: 14]
    "12" [r: 11]
    "13" [r: 12]
    "14" [r: 13]
    "15" [r: 16]
    "16" [r: 15]
    "17" [r: 18]
    "18" [r: 17]
    "19" [r: 19]
]

Now add the letter "O" to the list of keys to be watched, and run the above code when it's pressed. Also create an "old-r" variable to retain the number of the shape that needs to be erased. (Since the user changes shapes after the current one has been printed, we need to keep track of which one to erase):

keys: open/binary/no-wait [scheme: 'console]
forever [
    random/seed now
    r: random 19
    xpos: 18
    for i 1 25 1 [
        pos: to-pair rejoin [i "x" xpos]
        do compose/deep [prin tui [at (pos)] print tui shape/(r)]
        old-xpos: xpos 
        old-r: r
        if not none? wait/all [keys :00:00.30] [
            keystroke: to-string to-char to-integer copy keys
            switch keystroke [
                "k" [if (xpos > 5) [xpos: xpos - 1]]
                "l" [if (xpos < 30) [xpos: xpos + 1]]
                "o" [switch to-string r [
                    "1" [r: 2]
                    "2" [r: 1]
                    "3" [r: 6]
                    "4" [r: 3]
                    "5" [r: 4]
                    "6" [r: 5]
                    "7" [r: 10]
                    "8" [r: 7]
                    "9" [r: 8]
                    "10" [r: 9]
                    "11" [r: 14]
                    "12" [r: 11]
                    "13" [r: 12]
                    "14" [r: 13]
                    "15" [r: 16]
                    "16" [r: 15]
                    "17" [r: 18]
                    "18" [r: 17]
                    "19" [r: 19]
                ]]                  
            ]
        ]
        do compose/deep [
            prin tui [at (pos)] print tui shape/(old-r + 19)
        ]
    ]
    do compose/deep [prin tui [at (pos)] print tui shape/(r)]
]

The shapes are moving correctly now, but there's still a lot of work to be done. The first line of the last section of the overall game outline reads: "If the shape touches the bottom of the playing field, make it lock into the grid of other shapes that have already fallen". Right now the pieces all just fall to different stopping points in the playing field (depending on their height), and they don't stack on top of each other. Here's some pseudo code to fix that:

  1. I need to be aware of the highest coordinate in each column on the playing field. When the game starts, the highest coordinate in every column of the playing field is row 30 (the flat bottom line that makes up the playing field). I'll store each of these coordinates in a block called "floor".
  2. I also need to be aware of the lowest coordinate in each column of the currently falling shape. I'll make a block called "edge" to hold those coordinates (referring to the lower edges of the shape). Those coordinates will define the position of each of the lowest points in the currently falling shape, in relation to its top left point (the "pos" coordinate).
  3. Every time the shape falls one position down the screen, add each of the edge coordinates to the pos coordinate. If any of those coordinates is one position higher than the floor coordinate in the same column, then stop moving that shape (break out of the "for" loop that makes the shape fall). Use a foreach loop to cycle through the current coordinates in the relevant columns of each block, performing a comparison check on the floor and edge coordinates in each column.
  4. When a shape finishes its drop down the screen, calculate the new highest position in the columns it occupies (the coordinates of the top character in each column), and make those changes to the block that holds the high point information. To do that, I'll need to make a "top" block to hold the relative positions of the highest coordinates in the shape, and add them to the height of the current coordinates in the appropriate columns.

I'll start out simply, just getting each shape to lay flat on the floor of the playing field (row 30). For the moment, all I need to do is create a block of floor coordinates that represents that bottom line:

floor:  [30x1 30x2 30x3 30x4 30x5 30x6 30x7 30x8 30x9 30x10 30x11
    30x12 30x13 30x14 30x15 30x16 30x17 30x18 30x19 30x20 30x21
    30x22 30x23 30x24 30x25 30x26 30x27 30x28 30x29 30x30 30x31
    30x32 30x33 30x34 30x35]

Next, I'll define a set of lower coordinates for each shape, and store them in a nested block structure similar to the earlier "shape" block. "0x0" refers to the same coordinate as "pos" (0 positions to the right, and 0 positions down from "pos"). "0x10" is one position to the right, and "1x0" is one position down. I look at the visual representations of the shapes again to come up with the list:

;                   #
;   1   ####    2   #
;                   #
;                   #
;
;   3   ###     4    #      5    #      6   #
;        #          ##          ###         ##
;                    #                      #
;
;   7   ###     8   ##      9     #     10  #
;       #            #          ###         #
;                    #                      ##
;
;   11  ###     12  #       13  #       14  ##
;         #         #           ###         #
;                  ##                       #
;
;   15  ##      16   #
;        ##         ##
;                   #
;
;   17   ##     18  #
;       ##          ##
;                    #
;
;   19  ##
;       ##

Here's the complete set of low point definitions for each shape:

edge: [ [0x0 0x1 0x2 0x3] [3x0] [0x0 1x1 0x2] [1x0 2x1] 
    [1x0 1x1 1x2] [2x0 1x1] [1x0 0x1 0x2] [0x0 2x1] [1x0 1x1 1x2]
    [2x0 2x1] [0x0 0x1 1x2] [2x0 2x1] [1x0 1x1 1x2] [2x0 0x1]
    [0x0 1x1 1x2] [2x0 1x1] [1x0 1x1 0x2] [1x0 2x1] [1x0 1x1] ]

So, the relative coordinates of the low points in shape 3, for example, are referred to as edge/3. Here's some sample code to demonstrate how I can now refer to the bottom points in any shape using a foreach loop. The code "pos + position" refers to the low edge in each column:

pos: 5x5
r: 6
foreach position compose edge/(r) [print pos + position]

To check if any of those edges are touching the floor, use a foreach loop to cycle through the current coordinates in the relevant columns of each block, performing a comparison check on the floor and edge coordinates in each column. Here's some sample code to flesh out and test that idea:

pos: 30x10
for r 1 19 1 [
    print tui [clear]
    prin "Piece: " print r
    foreach po compose edge/(r) [
        print pos + po
        foreach coord floor [
            floor-y: to-integer first coord
            floor-x: to-integer second coord
            edge-y: to-integer first pos + to-integer first po
            edge-x: to-integer second pos + to-integer second po
            print rejoin [
                "edge: " edge-y "x" edge-x " "
                "floor: "floor-y "x" floor-x 
            ]
            if (edge-y >= floor-y) and (floor-x = edge-x) [
                print rejoin [ 
                    "You're touching or beyond the floor at: "
                    pos + po
                ]
            ]
        ]
    ]
    ask ""
]

Now let's integrate this technique into the existing code. We'll use a new variable "stop" to break out of the loop that drops the shape, when the current shape touches the floor:

keys: open/binary/no-wait [scheme: 'console]
forever [
    random/seed now
    r: random 19
    xpos: 18
    for i 1 32 1 [
        pos: to-pair rejoin [i "x" xpos]
        do compose/deep [prin tui [at (pos)] print tui shape/(r)]
        old-r: r
        old-xpos: xpos 
        if not none? wait/all [keys :00:00.30] [
            keystroke: to-string to-char to-integer copy keys
            switch keystroke [
                "k" [if (xpos > 5) [xpos: xpos - 1]]
                "l" [if (xpos < 30) [xpos: xpos + 1]]
                "o" [switch to-string r [
                    "1" [r: 2]
                    "2" [r: 1]
                    "3" [r: 6]
                    "4" [r: 3]
                    "5" [r: 4]
                    "6" [r: 5]
                    "7" [r: 10]
                    "8" [r: 7]
                    "9" [r: 8]
                    "10" [r: 9]
                    "11" [r: 14]
                    "12" [r: 11]
                    "13" [r: 12]
                    "14" [r: 13]
                    "15" [r: 16]
                    "16" [r: 15]
                    "17" [r: 18]
                    "18" [r: 17]
                    "19" [r: 19]
                ]]                  
            ]
        ]
        do compose/deep [
            prin tui [at (pos)] print tui shape/(old-r + 19)
        ]
        stop: false
        foreach po compose edge/(r) [
            foreach coord floor [
                floor-y: to-integer first coord
                floor-x: to-integer second coord
                edge-y:  i + to-integer first po
                edge-x:  xpos + to-integer second po
                if (edge-y >= floor-y) and (floor-x = edge-x) [
                    stop: true
                    break
                ]
            ]
        ]
        if stop = true [break]
    ]
    do compose/deep [prin tui [at (pos)] print tui shape/(old-r)]
]

This works, but there's a bug. If the piece has been spun around (using the "O" key), the new foreach loop fails to stop the piece from falling. That's because the foreach loop only cycles through the coordinates of the "edge/r" block. If the user flips the shape around, the "r" value gets changed before this code is run. The easiest way to fix this problem is to simply repeat the foreach loop using the "edge/old-r" block. This is an inefficient quick hack, but I'm writing this late at night - and there's some value to pointing out bad coding practice - so I choose to use that solution. I make a promise to myself to come up with a more elegant solution later... (Note to self: once a coding solution has been implemented, changes are harder to make, and bad code typically remains permanent ... I need to be careful about using quick hacks). Here's the current code:

keys: open/binary/no-wait [scheme: 'console]
forever [
    random/seed now
    r: random 19
    xpos: 18
    for i 1 32 1 [
        pos: to-pair rejoin [i "x" xpos]
        do compose/deep [prin tui [at (pos)] print tui shape/(r)]
        old-r: r
        old-xpos: xpos 
        if not none? wait/all [keys :00:00.30] [
            keystroke: to-string to-char to-integer copy keys
            switch keystroke [
                "k" [if (xpos > 5) [xpos: xpos - 1]]
                "l" [if (xpos < 30) [xpos: xpos + 1]]
                "o" [switch to-string r [
                    "1" [r: 2]
                    "2" [r: 1]
                    "3" [r: 6]
                    "4" [r: 3]
                    "5" [r: 4]
                    "6" [r: 5]
                    "7" [r: 10]
                    "8" [r: 7]
                    "9" [r: 8]
                    "10" [r: 9]
                    "11" [r: 14]
                    "12" [r: 11]
                    "13" [r: 12]
                    "14" [r: 13]
                    "15" [r: 16]
                    "16" [r: 15]
                    "17" [r: 18]
                    "18" [r: 17]
                    "19" [r: 19]
                ]]                  
            ]
        ]
        do compose/deep [
            prin tui [at (pos)] print tui shape/(old-r + 19)
        ]
        stop: false
        foreach po compose edge/(r) [
            foreach coord floor [
                floor-y: to-integer first coord
                floor-x: to-integer second coord
                edge-y:  i + to-integer first po
                edge-x:  xpos + to-integer second po
                if (edge-y = floor-y) and (floor-x = edge-x) [
                    stop: true
                    break
                ]
            ]
        ]
        foreach po compose edge/(old-r) [
            foreach coord floor [
                floor-y: to-integer first coord
                floor-x: to-integer second coord
                edge-y:  i + to-integer first po
                edge-x:  old-xpos + to-integer second po
                if (edge-y = floor-y) and (floor-x = edge-x) [
                    stop: true
                    break
                ]
            ]
        ]
        if stop = true [break]
    ]
    do compose/deep [prin tui [at (pos)] print tui shape/(old-r)]
]

Next, I decide to test the existing program for other bugs. I've been keeping separate text files containing all the code changes I make as I go along. Every time I make, test, and change a chunk of code, I save the new trial version with a new filename and version number. I save each version, just so that I don't permanently erase old code with each change - it may be potentially useful. My current working version is now #19.

I noticed during this debugging session that shape 1 still breaks through the right side of the wall. I could change that by adjusting the "(xpos < 30)" conditional expression that occurs when the "L" key gets pressed. But that solution will keep the other shapes from laying snugly against the wall. In fact, that additional problem is occurring now with shapes that are only 2 characters wide - I didn't notice until now. To deal with these problems, I create a block of values called "width", listing the widths of all 19 shapes, which can be used in the existing conditional expression:

width: [4 1 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 2]

Now I can check if the shape is at the right boundary, using the revised code below:

[if (xpos < (33 - compose width/(r))) [
    xpos: xpos + 1]
]

That check also needs to be performed every time the "O" key is pressed (we don't want the shape breaking out of the wall when it spins). I make the above changes to my current version of the program, and the problems are fixed.

The game is really starting to take shape! Now we need to make the shapes stack on top of each other. Earlier, I wrote these outline thoughts: "when a shape finishes its drop down the screen, calculate the new highest position in the columns it occupies (the coordinates of the top character in each column), and make those changes to the block that holds the high point information. To do that, I'll need to make a "top" block to hold the relative positions of the highest coordinates in the shape, and add them to the height of the current coordinates in the appropriate columns". Sounds like I'll need to loop through some columns to make the changes to the floor.

To create the "top" block I look at the visual representations of each shape once again, and come up with a coordinate list representing the high points in the shape, relative to the top left coordinate. It's similar to the "edge" block:

top: [ [0x0 0x1 0x2 0x3] [0x0] [0x0 0x1 0x2] [1x0 0x1] 
    [1x0 0x1 1x2] [0x0 1x1] [0x0 0x1 0x2] [0x0 0x1] [1x0 1x1 0x2]
    [0x0 2x1] [0x0 0x1 0x2] [2x0 0x1] [0x0 1x1 1x2] [0x0 0x1]
    [0x0 0x1 1x2] [1x0 0x1] [1x0 0x1 0x2] [0x0 1x1] [0x0 0x1] ]

The shape finishes its drop down the screen during the previous foreach loops we created, so to calculate the new highest positions in the columns occupied by the shape, I first need to determine which shape was the last one on the screen ("r" or "old-r"). The quick hack I made earlier is now coming back to bite me a bit - I now need to make duplicates of any changes that occur in both foreach loops:

stop-shape-num: r  
; (or stop-shape-num: old-r, depending on the foreach loop)
stop: true
break

Now to make the changes to the "floor" block, I loop through the columns occupied by the piece, setting each of the top characters in the shape to be the high coordinates in the respective columns of the floor. The "poke" function lets me replace the original coordinates in the floor block with the new coordinates. Those changes are made just before breaking out of the loop that drops the shape:

if stop = true [
    ; get the left-most column the last shape occupies:
    left-col: second pos 
    ; get the number of columns the shape occupies:
    width-of-shape: length? compose top/(stop-shape-num)
    ; get the right most column the shape occupies:
    right-col: left-col + width-of-shape - 1
    ; Loop through each column occupied by the shape, 
    ; replacing each coordinate in the current column
    ; of the floor with the new high coordinate:
    counter: 1
    for current-column left-col right-col 1 [
        add-coord: compose top/(stop-shape-num)/(counter)
        new-floor-coord: (pos + add-coord + -1x0)
        poke floor current-column new-floor-coord
        counter: counter + 1
    ]
    break
]

The new stacking code works, but there's a design flaw. If I maneuver a shape into an unoccupied space directly underneath any high point in the floor, without first touching the high point in that column, the piece doesn't stop. Furthermore, if that happens, it changes the new high point to the bottom of the column which the current shape occupies. I realize here that what I need to mark are not only the high points in the floor, but also every additional coordinate on the screen that contains a character. This is just as easy to accomplish. Instead of changing the current coordinates in the floor block (using the "poke" function):

poke floor current-column new-floor-coord

just add the new coordinates to the list (using "append"). That will keep track of all points at which a character is printed on the screen:

append floor new-floor-coord

That fixes the problem above, but I've also realized that if I move a shape sideways into an open position in the floor, the characters sometimes still overlap inappropriately. That's because the "top" and "edge" blocks only mark the highest and lowest points in each shape. It strikes me now that I could just combine those two blocks into one, marking all the coordinates occupied by a shape. Here's the new block - I call it "oc", short for "occupied":

oc:  [ 
    [0x0 0x1 0x2 0x3] [0x0 1x0 2x0 3x0] [0x0 0x1 0x2 1x1]
    [0x1 1x0 1x1 2x1] [0x1 1x0 1x1 1x2] [0x0 1x0 1x1 2x0]
    [0x0 0x1 0x2 1x0] [0x0 0x1 1x1 2x1] [0x2 1x0 1x1 1x2]
    [0x0 1x0 2x0 2x1] [0x0 0x1 0x2 1x2] [0x1 1x1 2x0 2x1]
    [0x0 1x0 1x1 1x2] [0x0 0x1 1x0 2x0] [0x0 0x1 1x1 1x2]
    [0x1 1x0 1x1 2x0] [0x1 0x2 1x0 1x1] [0x0 1x0 1x1 2x1]
    [0x0 0x1 1x0 1x1] 
]

I remove the "top" and "edge" blocks, and replace all code references to them with "oc".

Now there's another bug I need to fix. Sometimes when I press a key, the following error occurs:

** Script Error: Invalid argument: [45
** Where: to-integer            |
** Near: forall arg [change arg to-integer first arg]
arg: to-pair

The code referenced is not part of any code I've written. It seems to be related to keystroke input because it only happens when I press one of the game control keys. Since I'm not sure what's creating the error (maybe it's related to the timing of keystrokes, or perhaps it has to do with a key release), I make an educated guess and figure that the following line, which waits for keystrokes, is where it's occurring:

if not none? wait/all [keys :00:00.30] [...]

I wrap that whole thing in an error check:

if not error? try [if not none? wait/all [keys :00:00.30] [...]]

And, hmmm ... that doesn't work. So instead of guessing, I work methodically to check each of the other main sections of the program. Every section gets wrapped in an "error? try" routine, and I also put in an "if" conditional stucture to print out a numbered error message whenever an error occurs. I find that the error is first occurring here:

do compose/deep [prin tui [at (pos)] print tui shape/(r)]

Wrapped in the error test, that section looks like this:

if error? try [
    do compose/deep [
        prin tui [at (pos)] print tui shape/(r)
    ]
] [print "er1"]

I'm curious about what's causing the error, so I dig a little deeper. This time I have the error check print out the variables contained in the code:

if error? try [
    do compose/deep [
        prin tui [at (pos)] print tui shape/(r)
    ]
] [print rejoin [pos " " r]]

Nothing seems to be amiss. Every time the error occurs, the variables show a correct coordinate and shape number. So, for now I'll simply leave the error check in place, removing the printout. This will keep the game moving along whenever the ghostly error occurs. I'll need to post a message to the REBOL mailing list to see if anyone knows why the error is occurring. For the time being, the following error handler fixes the issue:

if error? try [
    do compose/deep [
        prin tui [at (pos)] print tui shape/(r)
    ]
] []

It turns out that I need to do the same thing for all the other similar occurences of code that print a shape to the screen:

if error? try [
    do compose/deep [
        prin tui [at (pos)] print tui shape/(old-r + 19)
    ]
] []

if error? try [
    do compose/deep [
        prin tui [at (pos)] print tui shape/(old-r)
    ]
] []

With all the known bugs controlled, I can move on to implementing the last parts of the game design. We need to check if the top row of the playing area is reached. If any shape stops moving at this ceiling row, end the game. This needs to be done any time a piece reaches it's final resting place, so I put it immediately after the main "for" loop in the program outline (so that it's evaluated immediately after the stopping code is executed):

if (first pos) < 2 [
    prin tui [at 35x0]
    print "Game Over"
    halt
]

Finally, to erase the bottom line of shapes every time a row is filled in horizontally, we're going to have to redraw the playing field entirely. The "floor" block contains all the information needed to rebuild the current state of the playing field (all the positions at which a character is currently printed). Here's an outline and some pseudo code to think through what needs to be done:

  1. Every time a shape stops moving, check to see if any row of the floor is full (i.e., there's one character printed in every column). I can use a for loop and a find function to perform that check on the floor block. (I'll start things off by just checking the bottom row).
  2. If any row is full (for now, just the bottom row), remove that row of characters from the floor block. Use a remove-each loop to remove any coordinates that have y positions in the relevant row from the floor block.
  3. Move all of the other characters above the relevant row down one row. Add one y position to all the other coordinates in the floor block which are above the relevant row. Use a foreach loop to go through each coordinate in the block and add 1x0. To replace the old floor block with the new one, first create a temporary block made up of the new floor block coordinates, then copy it back to the floor block once it's complete.
  4. Erase the current screen, print the static background, and then reprint a new playing field using the refreshed block of floor coordinates. We can accomplish this easily using a foreach loop and TUI to print the characters at each coordinate in the list.
; #1:

line-is-full: true
for colmn 5 32 1 [
    each-coord: to-pair rejoin [29 "x" colmn]
    if not find floor each-coord [
        line-is-full: false
        break
    ]
]

; #2:

if line-is-full = true [
    remove-each cor floor [(first cor) = 29]

; #3:

    new-floor: copy []
    foreach cords floor [
        append new-floor (cords + 1x0)
    ]
    floor: copy new-floor

; #4:

    prin tui [clear]
    a-line: copy [] loop 28 [append a-line " "] 
    a-line: rejoin ["   |"  to-string a-line "|"]
    loop 30 [print a-line] 
    prin "   " loop 30 [prin "+"] print ""
    foreach was-here floor [
        if not ((first was-here) = 30) [
            prin tui compose [at (was-here)]
            prin "#"
        ]
    ] 
]

At this point, I realize that I've made some logic errors in how the floor block and the stopping routine are structured. As it stands, when the screen is refreshed, the bottom row of the block (row 30) needs to be erased so that all the characters in row 29 can fall down one position. But if row 30 is erased, then the bottom of the floor disappears. As it turns out, row 31 should actually be treated as the bottom row, and all the characters should stop at 1x0 position higher then any character in the floor.

I make the required changes to the coordinates in the floor block (change all the y positions from 30 to 31). I also change the "new-floor-coord" variable in the stopping routine, and adjust the code above so that characters below line 30 are not printed. Additionally, the entire section above gets wrapped in a "for" loop to check if each row 1-30 is full. In the code above, I only checked if the bottom line was full - the number 29 referred to the row. I replace that number with the "row" variable created in the for loop. And with that, the last requirements of my original game outline are satisfied and an initial version of "Textris" is in working order. Here's the code:

REBOL [Title: "Textris"]

tui: func [
    {Cursor positioning dialect (iho)}
    [catch]
    commands [block!]
    /local screen-size string arg cnt cmd c err
][
    screen-size: (
        c: open/binary/no-wait [scheme: 'console]
        prin "^(1B)[7n"
        arg: next next to-string copy c
        close c
        arg: parse/all arg ";R"
        forall arg [change arg to-integer first arg]
        arg: to-pair head arg
    )
    string: copy ""
    cmd: func [s][join "^(1B)[" s]
    if error? set/any 'err try [
        commands: compose bind commands 'screen-size ][
        throw err
    ]
    arg: parse commands [
        any [
            'direct set arg string! (append string arg) |
            'home  (append string cmd "H") |
            'kill  (append string cmd "K") |
            'clear (append string cmd "J") |
            'up    set arg integer! (append string cmd [
                arg "A"]) |
            'down  set arg integer! (append string cmd [
                arg "B"]) |
            'right set arg integer! (append string cmd [
                arg "C"]) |
            'left  set arg integer! (append string cmd [
                arg "D"]) |
            'at   set arg pair! (append string cmd [
                arg/x ";" arg/y "H" ]) |
            'del   set arg integer! (append string cmd [
                arg "P"]) |
            'space set arg integer! (append string cmd [
                arg "@"]) |
            'move  set arg pair! (append string cmd [
                arg/x ";" arg/y "H" ]) |
            set cnt integer! set arg string! (
                append string head insert/dup copy "" arg cnt
            ) |
            set arg string! (append string arg)
        ]
        end
    ]
    if not arg [throw make error! "Unable to parse block"]
    string
]

shape: [
    ["####"]
    ["#" down 1 left 1 "#" down 1 left 1 "#" down 1 left 1 "#"]
    ["###" down 1 left 2 "#"]
    [right 1 "#" down 1 left 2 "##" down 1 left 1 "#"]
    [right 1 "#" down 1 left 2 "###"]
    ["#" down 1 left 1 "##" down 1 left 2 "#"]
    ["###" down 1 left 3 "#"]
    ["##" down 1 left 1 "#" down 1 left 1 "#"]
    [right 2 "#" down 1 left 3 "###"]
    ["#" down 1 left 1 "#" down 1 left 1 "##"]
    ["###" down 1 left 1 "#"]
    [right 1 "#" down 1 left 1 "#" down 1 left 2 "##"]
    ["#" down 1 left 1 "###"]
    ["##" down 1 left 2 "#" down 1 left 1 "#"]
    ["##" down 1 left 1 "##"]
    [right 1 "#" down 1 left 2 "##" down 1 left 2 "#"]
    [right 1 "##" down 1 left 3 "##"]
    ["#" down 1 left 1 "##" down 1 left 1 "#"]
    ["##" down 1 left 2 "##"]
    ;
    ["    "]
    [" " down 1 left 1 " " down 1 left 1 " " down 1 left 1 " "]
    ["   " down 1 left 2 " "]
    [right 1 " " down 1 left 2 "  " down 1 left 1 " "]
    [right 1 " " down 1 left 2 "   "]
    [" " down 1 left 1 "  " down 1 left 2 " "]
    ["   " down 1 left 3 " "]
    ["  " down 1 left 1 " " down 1 left 1 " "]
    [right 2 " " down 1 left 3 "   "]
    [" " down 1 left 1 " " down 1 left 1 "  "]
    ["   " down 1 left 1 " "]
    [right 1 " " down 1 left 1 " " down 1 left 2 "  "]
    [" " down 1 left 1 "   "]
    ["  " down 1 left 2 " " down 1 left 1 " "]
    ["  " down 1 left 1 "  "]
    [right 1 " " down 1 left 2 "  " down 1 left 2 " "]
    [right 1 "  " down 1 left 3 "  "]
    [" " down 1 left 1 "  " down 1 left 1 " "]
    ["  " down 1 left 2 "  "]
]

floor:  [
    31x5 31x6 31x7 31x8 31x9 31x10 31x11 31x12 31x13 31x14 31x15
    31x16 31x17 31x18 31x19 31x20 31x21 31x22 31x23 31x24 31x25
    31x26 31x27 31x28 31x29 31x30 31x31 31x32
]

oc:  [ 
    [0x0 0x1 0x2 0x3] [0x0 1x0 2x0 3x0] [0x0 0x1 0x2 1x1]
    [0x1 1x0 1x1 2x1] [0x1 1x0 1x1 1x2] [0x0 1x0 1x1 2x0]
    [0x0 0x1 0x2 1x0] [0x0 0x1 1x1 2x1] [0x2 1x0 1x1 1x2]
    [0x0 1x0 2x0 2x1] [0x0 0x1 0x2 1x2] [0x1 1x1 2x0 2x1]
    [0x0 1x0 1x1 1x2] [0x0 0x1 1x0 2x0] [0x0 0x1 1x1 1x2]
    [0x1 1x0 1x1 2x0] [0x1 0x2 1x0 1x1] [0x0 1x0 1x1 2x1]
    [0x0 0x1 1x0 1x1] 
]

width: [4 1 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 2]

a-line: copy [] loop 28 [append a-line " "] 
a-line: rejoin ["   |"  to-string a-line "|"]
loop 30 [print a-line] prin "   " loop 30 [prin "+"] print ""

keys: open/binary/no-wait [scheme: 'console]
forever [
    random/seed now
    r: random 19
    xpos: 18
    for i 1 30 1 [
        pos: to-pair rejoin [i "x" xpos]
        if error? try [
            do compose/deep [
                prin tui [at (pos)] print tui shape/(r)
            ]
        ] []
        old-r: r
        old-xpos: xpos 
        if not none? wait/all [keys :00:00.30] [
            keystroke: to-string to-char to-integer copy keys
            switch/default keystroke [
                "k" [if (xpos > 5) [
                        xpos: xpos - 1
                    ]]
                "l" [if (xpos < (33 - compose width/(r))) [
                        xpos: xpos + 1
                    ]]
                "o" [if (xpos < (33 - compose width/(r)))  [
                        switch to-string r [
                            "1" [r: 2]
                            "2" [r: 1]
                            "3" [r: 6]
                            "4" [r: 3]
                            "5" [r: 4]
                            "6" [r: 5]
                            "7" [r: 10]
                            "8" [r: 7]
                            "9" [r: 8]
                            "10" [r: 9]
                            "11" [r: 14]
                            "12" [r: 11]
                            "13" [r: 12]
                            "14" [r: 13]
                            "15" [r: 16]
                            "16" [r: 15]
                            "17" [r: 18]
                            "18" [r: 17]
                            "19" [r: 19]
                        ]
                    ]
                ]                   
            ] []
        ]
        if error? try [
            do compose/deep [
                prin tui [at (pos)] print tui shape/(old-r + 19)
            ]
        ] []
        stop: false
        foreach po compose oc/(r) [
            foreach coord floor [
                floor-y: to-integer first coord
                floor-x: to-integer second coord
                oc-y:  i + to-integer first po
                oc-x:  xpos + to-integer second po
                if (oc-y = (floor-y - 1)) and (floor-x = oc-x) [
                    stop-shape-num: r
                    stop: true
                    break
                ]
            ]
        ]
        foreach po compose oc/(old-r) [
            foreach coord floor [
                floor-y: to-integer first coord
                floor-x: to-integer second coord
                oc-y:  i + to-integer first po
                oc-x:  old-xpos + to-integer second po
                if (oc-y = (floor-y - 1)) and (floor-x = oc-x) [
                    stop-shape-num: old-r
                    stop: true
                    break
                ]
            ]
        ]
        if stop = true [
            left-col: second pos 
            width-of-shape: length? compose oc/(stop-shape-num)
            right-col: left-col + width-of-shape - 1
            counter: 1
            for current-column left-col right-col 1 [
                add-coord: compose oc/(stop-shape-num)/(counter)
                new-floor-coord: (pos + add-coord)
                append floor new-floor-coord
                counter: counter + 1
            ]
            break
        ]
    ]
    if (first pos) < 2 [
        prin tui [at 33x0]
        print "GAME OVER!!!"
        halt
    ]
    if error? try [
        do compose/deep [
            prin tui [at (pos)] print tui shape/(old-r)
        ]
    ] []
    for row 1 30 1 [
        line-is-full: true
        for colmn 5 32 1 [
            each-coord: to-pair rejoin [row "x" colmn]
            if not find floor each-coord [
                line-is-full: false
                break
            ]
        ]
        if line-is-full = true [
            remove-each cor floor [(first cor) = row]
            new-floor: copy [
                31x5 31x6 31x7 31x8 31x9 31x10 31x11 31x12 31x13
                31x14 31x15 31x16 31x17 31x18 31x19 31x20 31x21
                31x22 31x23 31x24 31x25 31x26 31x27 31x28 31x29
                31x30 31x31 31x32
            ]
            foreach cords floor [
                either ((first cords) < row) [
                    append new-floor (cords + 1x0)
                ][
                    append new-floor cords
                ]
            ]
            floor: copy unique new-floor
            prin tui [clear]
            a-line: copy [] loop 28 [append a-line " "] 
            a-line: rejoin ["   |"  to-string a-line "|"]
            loop 30 [print a-line] 
            prin "   " loop 30 [prin "+"] print ""
            foreach was-here floor [
                if not ((first was-here) = 31) [
                    prin tui compose [at (was-here)]
                    prin "#"
                ]
            ]
        ]
    ]
]

Now that the program is working to my original specs, I want to make it look a bit spiffier. First of all, the playing area looks too wide and tall. I check Rebtris, and it's only 10 columns wide by 20 rows tall. I like that look and feel, so I adjust the floor block, the code that draws the static backdrop, and all computations related to the right boundaries of the playing field and the number of rows, to reflect that change.

I also want to print out a "Textris" title header, some keyboard instructions, and a score header. Tui allows me to print this text to the right of the playing field where I want it:

print tui [
    at 4x21 "TEXTRIS" at 5x21 "-------" 
    at 7x20 "'K' = left" at 8x20 "'L' = right"
    at 9x20 "'O' = spin" at 11x21 "Score:" 
]

Keeping track of the score is simple. When the program starts, a "score" variable is created and set to 0 ("score: 0"). Every time a piece stops falling, 10 points are added to the score. That number is printed beneath the score header (notice that the score number must first be converted to a string, in order to be printed by tui):

score: score + 10
print tui compose [at 13x21 (to-string score)]

Every time a row is filled in, 1000 points are added to the score. When the screen if redrawn to reflect the newly erased row, the tui code that prints the backdrop also prints out the updated score:

print tui compose [
    at 4x21 "TEXTRIS" at 5x21 "-------" 
    at 7x20 "'K' = left" at 8x20 "'L' = right"
    at 9x20 "'O' = spin" at 11x21 "Score:" 
    at 13x21 (to-string score)
]

Next, I want to add a pause key. This will fit in the switch structure that watches for keystrokes. Whenever the "P" key is pressed, print a message indicating that the game has been paused. Use an "ask" action to wait for input, and then print two blank lines to erase the pause message and any errant characters that the user may type in before hitting the [Enter] key:

"p" [
    print tui [
        at 23x0 "Press [Enter] to continue"
    ]
    ask ""
    print tui [
        at 24x0 "                              "
        at 23x0 "                              "
    ]
]

After posting some of this code to the REBOL mail list, another bug has become obvious. If the insert key or the arrow keys are pressed during game play, the game crashes. The following code produces a "** Math Error: Math or number overflow" when those keys are evaluated:

keystroke: to-string to-char to-integer copy keys

To fix that, I create my own error check. The keys codes for the arrow keys are #{1B5B41}, #{1B5B42}, #{1B5B43}, #{1B5B44}, and #{1B5B327E}. I check to see if they've been pressed first. If not, run the code above:

now-key: copy keys
if not (
    find [
        #{1B5B41} #{1B5B42} #{1B5B43}
        #{1B5B44} #{1B5B327E}
    ] (now-key)
) [keystroke: to-string to-char to-integer now-key]

That works, but a message to the list by Gabrielle Santilli creates a simpler solution. It turns out that I should have looked at the console port format a bit more carefully. All that's needed to get the keystroke is:

keystroke: to-string copy keys

And that does not produce errors for any entered keys.

I added all the above code to the program, and then tested everything. In doing so, I made an interesting discovery - it turns out that the code which produced the ghostly key input error in the shape printing routines is in a section of the TUI dialect that enables one to check for screen size. I think the error has something to do with the fact that I'm "compose"ing the results - not sure, but it doesn't matter. Since I'm not using that function, I simply remove it from the code. While I'm at it, I remove all the other parts of the TUI dialect that I'm not using. It turns out that all I need is:

tui: func [commands [block!]] [
    string: copy ""
    cmd: func [s][join "^(1B)[" s]
    arg: parse commands [
        any [
            'clear (append string cmd "J") |
            'up    set arg integer! (append string cmd [
                arg "A"]) |
            'down  set arg integer! (append string cmd [
                arg "B"]) |
            'right set arg integer! (append string cmd [
                arg "C"]) |
            'left  set arg integer! (append string cmd [
                arg "D"]) |
            'at   set arg pair! (append string cmd [
                arg/x ";" arg/y "H" ]) |
            set arg string! (append string arg)
        ]
        end
    ]
    string
]

With that error gone, I can remove all the error checking routines in the program (they were causing some additional problems). Now Textris feels like a reasonably complete program. Here's the final code:

REBOL [Title: "Textris"]

tui: func [commands [block!]] [
    string: copy ""
    cmd: func [s][join "^(1B)[" s]
    arg: parse commands [
        any [
            'clear (append string cmd "J") |
            'up    set arg integer! (append string cmd [
                arg "A"]) |
            'down  set arg integer! (append string cmd [
                arg "B"]) |
            'right set arg integer! (append string cmd [
                arg "C"]) |
            'left  set arg integer! (append string cmd [
                arg "D"]) |
            'at   set arg pair! (append string cmd [
                arg/x ";" arg/y "H" ]) |
            set arg string! (append string arg)
        ]
        end
    ]
    string
]

shape: [
    ["####"]
    ["#" down 1 left 1 "#" down 1 left 1 "#" down 1 left 1 "#"]
    ["###" down 1 left 2 "#"]
    [right 1 "#" down 1 left 2 "##" down 1 left 1 "#"]
    [right 1 "#" down 1 left 2 "###"]
    ["#" down 1 left 1 "##" down 1 left 2 "#"]
    ["###" down 1 left 3 "#"]
    ["##" down 1 left 1 "#" down 1 left 1 "#"]
    [right 2 "#" down 1 left 3 "###"]
    ["#" down 1 left 1 "#" down 1 left 1 "##"]
    ["###" down 1 left 1 "#"]
    [right 1 "#" down 1 left 1 "#" down 1 left 2 "##"]
    ["#" down 1 left 1 "###"]
    ["##" down 1 left 2 "#" down 1 left 1 "#"]
    ["##" down 1 left 1 "##"]
    [right 1 "#" down 1 left 2 "##" down 1 left 2 "#"]
    [right 1 "##" down 1 left 3 "##"]
    ["#" down 1 left 1 "##" down 1 left 1 "#"]
    ["##" down 1 left 2 "##"]
    ;
    ["    "]
    [" " down 1 left 1 " " down 1 left 1 " " down 1 left 1 " "]
    ["   " down 1 left 2 " "]
    [right 1 " " down 1 left 2 "  " down 1 left 1 " "]
    [right 1 " " down 1 left 2 "   "]
    [" " down 1 left 1 "  " down 1 left 2 " "]
    ["   " down 1 left 3 " "]
    ["  " down 1 left 1 " " down 1 left 1 " "]
    [right 2 " " down 1 left 3 "   "]
    [" " down 1 left 1 " " down 1 left 1 "  "]
    ["   " down 1 left 1 " "]
    [right 1 " " down 1 left 1 " " down 1 left 2 "  "]
    [" " down 1 left 1 "   "]
    ["  " down 1 left 2 " " down 1 left 1 " "]
    ["  " down 1 left 1 "  "]
    [right 1 " " down 1 left 2 "  " down 1 left 2 " "]
    [right 1 "  " down 1 left 3 "  "]
    [" " down 1 left 1 "  " down 1 left 1 " "]
    ["  " down 1 left 2 "  "]
]
floor:  [
    21x5 21x6 21x7 21x8 21x9 21x10 21x11 21x12 21x13 21x14 21x15
]
oc:  [ 
    [0x0 0x1 0x2 0x3] [0x0 1x0 2x0 3x0] [0x0 0x1 0x2 1x1]
    [0x1 1x0 1x1 2x1] [0x1 1x0 1x1 1x2] [0x0 1x0 1x1 2x0]
    [0x0 0x1 0x2 1x0] [0x0 0x1 1x1 2x1] [0x2 1x0 1x1 1x2]
    [0x0 1x0 2x0 2x1] [0x0 0x1 0x2 1x2] [0x1 1x1 2x0 2x1]
    [0x0 1x0 1x1 1x2] [0x0 0x1 1x0 2x0] [0x0 0x1 1x1 1x2]
    [0x1 1x0 1x1 2x0] [0x1 0x2 1x0 1x1] [0x0 1x0 1x1 2x1]
    [0x0 0x1 1x0 1x1] 
]
width: [4 1 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 2]
score: 0

prin tui [clear]
a-line: copy [] loop 11 [append a-line " "] 
a-line: rejoin ["   |"  to-string a-line "|"]
loop 20 [print a-line] prin "   " loop 13 [prin "+"] print ""
print tui compose [
    at 4x21 "TEXTRIS" at 5x21 "-------" 
    at 7x20 "Use arrow keys" at 8x20 "to move/spin."
    at 10x20 "'P' = pause"
    at 13x20 "SCORE:  " (to-string score)
]

keys: open/binary/no-wait [scheme: 'console]
forever [
    random/seed now
    r: random 19
    xpos: 9
    for i 1 20 1 [
        pos: to-pair rejoin [i "x" xpos]
        do compose/deep [prin tui [at (pos)] print tui shape/(r)]
        old-r: r
        old-xpos: xpos 
        if not none? wait/all [keys :00:00.30] [
            switch/default to-string copy keys [
                "p" [
                    print tui [
                        at 23x0 "Press [Enter] to continue"
                    ]
                    ask ""
                    print tui [
                        at 24x0 "                              "
                        at 23x0 "                              "
                    ]
                ]
                "^[[D" [if (xpos > 5) [
                        xpos: xpos - 1
                ]]
                "^[[C" [if (xpos < (16 - compose width/(r))) [
                        xpos: xpos + 1
                ]]
                "^[[A" [if (xpos < (16 - compose width/(r)))  [
                        switch to-string r [
                            "1" [r: 2]
                            "2" [r: 1]
                            "3" [r: 6]
                            "4" [r: 3]
                            "5" [r: 4]
                            "6" [r: 5]
                            "7" [r: 10]
                            "8" [r: 7]
                            "9" [r: 8]
                            "10" [r: 9]
                            "11" [r: 14]
                            "12" [r: 11]
                            "13" [r: 12]
                            "14" [r: 13]
                            "15" [r: 16]
                            "16" [r: 15]
                            "17" [r: 18]
                            "18" [r: 17]
                            "19" [r: 19]
                        ]
                    ]
                ]                   
            ] []
        ]
        do compose/deep [
            prin tui [at (pos)] print tui shape/(old-r + 19)
        ]
        stop: false
        foreach po compose oc/(r) [
            foreach coord floor [
                floor-y: to-integer first coord
                floor-x: to-integer second coord
                oc-y:  i + to-integer first po
                oc-x:  xpos + to-integer second po
                if (oc-y = (floor-y - 1)) and (floor-x = oc-x) [
                    stop-shape-num: r
                    stop: true
                    break
                ]
            ]
        ]
        foreach po compose oc/(old-r) [
            foreach coord floor [
                floor-y: to-integer first coord
                floor-x: to-integer second coord
                oc-y:  i + to-integer first po
                oc-x:  old-xpos + to-integer second po
                if (oc-y = (floor-y - 1)) and (floor-x = oc-x) [
                    stop-shape-num: old-r
                    stop: true
                    break
                ]
            ]
        ]
        if stop = true [
            left-col: second pos 
            width-of-shape: length? compose oc/(stop-shape-num)
            right-col: left-col + width-of-shape - 1
            counter: 1
            for current-column left-col right-col 1 [
                add-coord: compose oc/(stop-shape-num)/(counter)
                new-floor-coord: (pos + add-coord)
                append floor new-floor-coord
                counter: counter + 1
            ]
            break
        ]
    ]
    do compose/deep [prin tui [at (pos)] print tui shape/(old-r)]
    if (first pos) < 2 [
        prin tui [at 23x0]
        print "   GAME OVER!!!^/^/"
        halt
    ]
    score: score + 10
    print tui compose [at 13x28 (to-string score)]
    for row 1 20 1 [
        line-is-full: true
        for colmn 5 15 1 [
            each-coord: to-pair rejoin [row "x" colmn]
            if not find floor each-coord [
                line-is-full: false
                break
            ]
        ]
        if line-is-full = true [
            remove-each cor floor [(first cor) = row]
            new-floor: copy [
                21x5 21x6 21x7 21x8 21x9 21x10 21x11 21x12 21x13
                21x14 21x15
            ]
            foreach cords floor [
                either ((first cords) < row) [
                    append new-floor (cords + 1x0)
                ][
                    append new-floor cords
                ]
            ]
            floor: copy unique new-floor
            score: score + 1000
            prin tui [clear]
            loop 20 [print a-line] 
            prin "   " loop 13 [prin "+"] print ""
            print tui compose [
                at 4x21 "TEXTRIS" at 5x21 "-------" 
                at 7x20 "Use arrow keys" at 8x20 "to move/spin."
                at 10x20 "'P' = pause"
                at 13x20 "SCORE:  " (to-string score)
            ]
            foreach was-here floor [
                if not ((first was-here) = 21) [
                    prin tui compose [at (was-here)]
                    prin "#"
                ]
            ]
        ]
    ]
]

Here's a quick synopsis of the program:

  • The TUI dialect is defined.
  • The "shape" block, containing the TUI instructions for drawing each shape is defined.
  • The "floor", "oc", and "width" coordinate blocks are defined. The "score" variable is also defined.
  • The backdrop characters (left, right, and bottom barriers), instructions, headers, and score are printed.
  • A forever loop runs the main actions of the program. The subsections of that loop are:
    • A shape is printed on the screen.
    • User keystrokes are watched for.
    • A switch structure decides what to do with entered keystrokes (l = move right, k = move left, o = rotate shape, p = pause).
    • Another switch structure determines which shape to print when the current shape is rotated.
    • The currently printed shape is erased.
    • Two foreach loops check whether the current shape has reached a position at which it should stop falling.
    • If the piece has reached a stopping point, the coordinates occupied by the piece are added to the "floor" block.
    • The shape is printed at its final resting place.
    • If the current shape touches the ceiling, the game ends.
    • The score is updated.
    • If any rows have been completely filled in, their coordinates are removed from the floor block, the coordinates of all other characters are moved down a row, and the screen is reprinted with the new floor coordinates and the new score.
    • The forever loop continues.

If I'd been so thoughtful and organized as to write a structured outline like that in the beginning of the case study, things would've moved along more quickly. But any project is easier in retrospect ... I just try to remember that building as detailed an outline as possible before writing any code always saves a great deal of work and confusion.

Now that the game satisfies my original intentions, I'll bring the case study to a close, but not without first putting together a to-do list of things to improve in the program. If you'd like to try implementing some of these changes, first figure out where in the outline they should go, write some pseudo code to get the job done, and then come up with REBOL code to satisfy those pseudo code expressions:

  1. Save high scores to disk.
  2. Add a way to incrementally increase the speed at which shapes drop. Do this every time a certain number of rows is cleared.
  3. Add a "next piece" preview.
  4. Look for a way to remove the cursor from the printout, so that it's not visible along the left side of the wall as the shapes fall.
  5. Add sound. Play midi tones for each event that occurs, and play a background tune while the game is running.
  6. Rewrite the entire program using GUI techniques, instead of console text characters and TUI.

Looking at my coding process in retrospect, I should note some criticisms. One element that annoyed me was a set of badly chosen variable names. I initially used "r", for example, to represent the current shape number because it was first used to represent a random number. "R" is not so descriptive, and it was hard to remember what "r" represented while I was coding. The same was true of "i", which became more important as the loop that dropped the shapes grew in complexity. I left those variables as they were in this case study so that the lines of code fit neatly onto this web page, but in my own coding I choose to use more descriptive variables. Doing that in general makes code more readable and easier to think through.

The Moral of the Story:

Whether or not you're interested in game programming, and despite the fact that the final product of this case study is a bland implementation of Tetris, some general understanding about coding can be gained from the thought process covered in this section. It's typical of any general coding project you'll encounter: start with a design concept, outline the main structure of the program you imagine, use pseudo code to guide you from the "what am I trying to do?" through the "how do I code it" stages, and refine the detail of your outline by testing and experimenting with small code chunks along the way.

In general, if you can't think through the process of "what am I trying to accomplish" in a structured way, then you won't be able to write the code to accomplish it. Once you've got a basic grasp of language concepts and syntax, you'll see that writing code just takes lots of creative organization and experimentation. Keep a language reference close at hand, and you can work out the syntax of virtually any code you need to write. That's only a matter of knowing which functions and constructs are available to solve your problems, and looking up the format for those you're not familiar with. The difficult part in any coding situation is mapping each small thought process to a data construct, conditional expression, looping routine, function definition, existing code module, word label, etc. For large projects, you'll typically need an outline because it's so easy to get lost in the minute coding details along the way. Start with a top down approach, conceive and design a flow chart/outline, and then flesh out the details of each section until you've got code written to solve each design concept. Once you become familiar with that process, experience will show that you can code solutions for virtually any problem you encounter.

You'll find that in many cases REBOL allows you to think directly in code more easily than you can with pseudo code. That's because Rebol's high level design is meant to be human readable and human "thinkable". Although many coding concepts in all computer languages are generally the same, most other languages are more overtly designed and constrained by legacy concepts derived from requirements about how computers operate. Some languages tend to require much more low level coding or coersing of disparate modules to fit together in order to make the conceptual design take shape in final code form. Other languages get you bogged down in thinking about higher level OOP constructs. A lack of universal data structures such as Rebol's block/series structure, a lack of built in native data types such as time, tuples, pairs, money, etc., and a less natural way of structuring functions, variables and module definitions (not using words and dialects in a natural language way), require unique and contrived constructs to be designed to manipulate data in each individual program. In the most popular languages, program authors typically have to be more concerned about managing the rudimentary memory and cpu actions of the computer for everything that occurs in a program. That enables a greater measure of control over how a computer uses it's hardware resources, but it's very far from the way humans naturally think about solving real life situations. REBOL allows things to be done in a way of thinking that's closer to the outline stage. When you get used to writing REBOL code, you'll find that it saves a tremendous amount of time compared to other languages. Remember along the way that no matter what computer language(s) you learn, understanding how to think through the "what I am I trying to accomplish" outline is essential to writing code that accomplishes your task.

31.6 Case 5 - Coding a Freecell Game Clone (GUI)

The Textris project was entertaining and educational, so I'm motivated to create a simple GUI game next. As far as I know, there's no existing Freecell game implemented in REBOL, and it's my other favorite recreational program. This project will provide some more food for thought about useful GUI techniques and approaches. Here's my initial outline:

  1. Get the card images compressed and embedded into REBOL code.
  2. Write the code to display and move cards around the screen. It will be similar to that found in the Guitar Chord Diagram Maker example presented earlier. I'll need to click and drag images around the screen. I'll also want to make the images "snap" into position onto other cards, rather than floating freely.
  3. Create a nice looking GUI layout backdrop for the playing field.
  4. Layout the cards in random order, in 8 piles, on the playing field.
  5. Allow the selection and movement of cards, based on the rules of Freecell (i.e., cards need to be placed in descending order, red-black-red-black, goal piles must start with aces, and ascend through a single suit, etc.). These rules can be handled by a series of conditional evaluations that are run every time a card is moved. This step will require the most coding thought and will likely need a sub-outline.

To get started with the first step, I remembered seeing a REBOL card game at http://www.rebolfrance.org/articles/bridge/bridge.html . The zip package at that location contains all the .bmp card images in a single directory. I downloaded the package and wrote a little variation of the compression program provided earlier in this tutorial. It loops through all the cards in the directory, reads and compresses the files, and then appends each unit of data to a single block labeled "cards", which is created to hold all the images:

REBOL [Title: "REBOL Binary Embedder"]

system/options/binary-base: 64
cards: copy []
foreach file load %./  [
    uncompressed: read/binary file
    compressed: compress to-string uncompressed
    ; there are some other files in the directory that I don't
    ; want to embed.  Limiting the file size to 10k weeds them out:
    if ((length? uncompressed) < 10000) [
        append cards compressed
    ]
]
editor cards

Because the cards are read in alphabetical order from the directory, I need to change the order of the card data so that they ascend in the following order: Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K. I also added some comments to clarify where each suit begins and ends. This provides a nice chunk of data that I can use to build other card games of any type:

cards:  [

; clubs:
64#{
eJzt1z0WwiAMAODoc1Wu0OfkOVycvIOncOZoHTwQk2sMBAehL8mzqTqYFlH6Pegj
2J/j+b6FElcqByonKhcqK9iU9kjHbzsurxHLDjFylTf6Mo4j1bkFyw6IXOUtN9HH
vu2qi/UwoBZpCKpBcDDBxyTwMZCChyEBquH8iSanK2iGh5NMyp3AfPMccb4x5QIM
ufAxkECfQwB9Dn0MHQ1q3t3WfB3xb75joGvqTUmjaEiEVrUG8rJqGpufqd4jPmGQ
iXg+1FHeUDSmOUzt2SxonHI6FX/zW6bP4luGL/iiSf0fajFTb4iymVjlyxnLPGth
M/VBaLapD2aK6S6AvZm44vSmDCcbVFJqNk5rnh/sPYwSJmN5J7K8Wz0AAI/VC/YN
AAA=
} 64#{
eJztl7FRxTAMhgVHC14hR8UcNFTswBTUGS0FA6miNbKd49nSn0g5KC1Hzy/yF1vy
+SLl9f37kap8ir6Ivol+iN7RQ7WvMv711HSUtV60rq0rTf5s2yZ9seR6Uc6tK62Y
5OdZT2XkflmyJ7wkl8n0d4ZrDOeMuJxcJnOA8f2pw67PkBkt5c4wNdpxIsPUaDuf
UeyaQbErBu7h6A9kKJWmbXoWojEyw+xHL92e8RkCexhhxB/uI0OM3HNnIyZ4fjpL
i/J8D48YxbuMjCb/zB+cw8vMvuJkJjOZyUzmCsP0L0x74Z8yDLKsw7Dl7Tww67WE
eHsG5M89sf6uxGY1xejcfYnhPp8fMJ1EapKj2macG+4hqq4sk9lnks+wKhqRP6D6
tEzkrIYKZHYZijA2WsOAaIE/joSYyDdR5NvqB5uyj432DQAA
} 
64#{
eJztlzF2wyAMhpW+ri1X8OuUc3TJ1Dv0FJ19NA89kKasREDei4V+R4obv3QImNiR
P7AEQsDn1/GNavqRspdykPItZUevVT7K+9/3VnQa60Xj2G4ly8M0TXIvklwvyrnd
Si4i+fnomzLpZRiyl3hILpPp74yok+7BcGKXyeTrIw25DNd+N4ySEGRqTaWOZaq1
NzLI9o6Bfaj1gXZRKrmX9a0QqZYsc3a9dKnjM/VxBSP68NwyxMh/nsmICfrPTNKs
vN6HS0zHu4y8TQGfD/hzhDl/8ck8hOF+5rixBUq0P0OmnxeI6efXlkwkbkTiT6wP
jTYbMncaU5SezP9i2Iz0KqYF/KsMg9niMNAP+3agH7YF8VIHxha1ni/EFrVWL8SE
CMPz9Xzb2AJ2RYYBuyvLZHaZfDOD9WFdE44pqGm/5SBtLLx9dmoHEo+x1q5i3BRi
ImeiyNnqBBVpT9z2DQAA
} 64#{
eJztlz1WwzAMgAWPFXyFPCbOwcLEHTgFc46WgQNpYhWSVce/WCp0tB3Xrvw1kiyn
cl7fvx8hlk9uL9zeuH1wu4OHKN95/utJW132eMG+ayeVB8dxcC8SihcQaSdVRPzx
3N6qK/fbRlbBLZgMwf8ZDA4GPExwMOCwhwGTYYtKphxHY0UVVEzUffJxomMA8hcE
/UG7Pn9loFnDgT0tA0FqwqV2/qJuqFNtoTkPCvmFhzamYwaqmHoYliNme0LQ6Xpv
QEDKfpEEk8S9MI+p6pyvYc/0xcOQmG7vQ9dzYTMXjYtZzGIWs5hrGISbMPqHP2Ww
yLUmM8rv3X36c0u2JybEYa7MfqWcN8i5NTPO3VcxmsjmDBXyZM/wTGKcbfDU8Nsa
nszorJV1Ad2AweSOFdPGxzamCOTZhzaD812YYmoznmfHYbNZXIznncjzbvUDyCYa
mfYNAAA=
} 64#{
eJztlzF2wyAMhtW+rg1X8OvUc3Tp1Dv0FJ19NA85kKauVEIGDFKAuM5rhwgTiPwF
fsBG5O3j+xmCfVF+pfxO+ZPyAzwF/0z3zyfJpc3hgnmWghNVlmWhkj0+XOC9FJzY
RR8vdVPKHqfJ9wwn12U8/J4hOe4IhhQfwuAePds6grgqPwJsG3AWA5C/IMgPoNK8
m6k0W3qCLzPgOEWckxovygOVut30nCsb/8rDOk0dJjiuYsiPmPU4J7cLhmro87i8
Yyk8vM6aSp/tOdSMthHGs/SBZ7XSuZNZe7wzf8OAcmkGofbUTHySW0x6Iy4z+c26
PVPtGDZT7jw2MzSHWKu5IXPQmlp2Z/4Xo1dxFyMbfpNB/UJdZqz4rtoxzi1JTwiI
ZqzM44oxz4i5JWPH7qsYCWRtxm/8UY95JmmfbeJoGnOYzljWWSsxfC47gPEr09IT
meaa6l2pNGGaiGju2CgzpqfLdG2I6Sqm6VR/05Sd4Acse9Xu9g0AAA==
} 64#{
eJztlj1WwzAMgAWPFXyFPCbOwcLEHTgFc46WgQNpYjWS5dS/SHo07WOoHDep/MWS
bVnO6/v3IyT5pPpC9Y3qB9U7eEj6ldq/nqS2sqYL1lVuXOhh2za6syamC2KUGxdW
0c9z39Ug98sSLcElmEyE8xnkdpMBhy0CTAZNhmyZDPRM/Ywgqs5WGkPpIMwYgPIH
QV44jOl8nvnTzTMELjvOZRgvSkCdzFaWy0OlzzzkaTIYaGLDw5AesfgTgjS3MQYB
YxlXDOwKD89YU7Gpz+HIjOJhIrvuiNXOzz8y2eKNuTIz24NDP5Pc0uln8dwx833R
MvP9dRnGkzc8+cc3h7N8eCnmoDX9XW7M/2Lq9TuDkYSvMlhvJYtR4rD0o8ShHIhG
btnPPC235BNYzQkeRg6yq+SWfTTaXt571XJC+i47kFH9yYy6pvU7MxFGRcSWIfan
SzPPGhM9jMMfmzFHdYo4XX4AibGWIfYNAAA=
} 64#{
eJztlzF2wyAMQNW+ri1X8OvUc2Tp1Dv0FJ19NA85kKauVAJiQMigpHamCBOI+AaB
QDyfvn5fIcgP5Q/Kn5S/KT/BS9DP1H5+i7mWOTwwz7HgRJVlWahkjQ8PeB8LTqyi
n3fZVSPP0+RHgpMbMh7+z5A5bhfG45BBN7bHwngQTFlHUBkEKPjUIBiA/AchvcAG
HcCo9tQMOE4XnFMzX4wbah22GDlXCn3iIS3TgAmKqxjSI2Z7nIvNIOaFPs/LOzaF
p+f6Po1j9tewZVqxMJ5NH+9nuQ9vZNKID+bOjHoGZT9abKn12n4WjH4uakY/X8cw
lrhhiT+2NVTj4UHMFT6NrbpPc3dSI5na4zpTe1xlhKcOZYTHdab2uM7UK7zBoLRm
X6a2UbenZlSfxoDfETrl7cJuM519mPvpxJZ4IQ5iy+XO68WWdAN3Y4KFiRfZfWIL
rD1treHKdGPCXgy6sT2J6d4XWLRpEt7t7jAzA6PBmGlPy03MUEyM5ZvI8m31B3Qa
a6P2DQAA
} 64#{
eJzVlz12wyAMx9W+rC1X8MuUc3Tp1Dv0FJ19NA85kKasRBIkNRhLcmK7r8KECP8e
SPD318fX5Q3EfqieqH5S/ab6Agfp7+n8+T3V0no5oO9Tw4X+DMNALfdEOSDG1HDh
Lvo51kNN7LXromXYBZOJ8DyDkoPOUMjBZCLuxtQxIxQnmzHDeMzsVHOh7GflrMY4
4inzQuDScn6ZpKfQcO4MZtFVjgxmMDLYEoa9MIonOyVDM4dRXsnh9IK+p2lOfQ2n
zNQ8TOTQHTqMjuvLZvKMi5iW5mumpbGKaWp1I8YRz7aa34SZ0XydV0Pzk/VZaU/n
7Y8YT8x27p41dOzFztowterS/H+8ln16njUvU0zyOJNu+CqD4y22GGV97oy2PumB
aGj+9nTVNM+epVUPg7Cb5hFu73DzGsujqlqV97L1GE886p6irp5irl2YIqE244nZ
NBfj+SbyfFtdAaNeJZ72DQAA
} 64#{
eJzVljtyxCAMhpVM2oQreFLtOdKkyh1yitQ+moscSFVaViDMUyB2vE4msNgr/I0R
4kf47ePnGXz5onah9k7tk9oDPPn+lZ5/v3Ary+p/sK58c5X+bNtGd9dj/Q+s5Zur
rosur/WrmvK4LFYruBiVsXCcQT+HMUMuG5Wx+GcMQvYwGLXPkL8zGFDOHb1dGXxR
GKMzdZwlf2zBILgqGYlhPRnBiAwG0VWGf5nC+JfdwjjLZP4Eo2RoZJPNiw03PWVN
ecxxDFumLTOMda6rOixieIAJI97ESJqvGVFjJSNq9SRmwp9zNX8KE2WOYTuY1p+k
eRZptgGaNQWDO9WLIY2EGDdfy6TA1J3nMFgERvaZbFe7PmPKkp25x1TBIRBjuIef
s7+4FnEZHaOtO3Iba4NXpKMNVatTmv+Pe3kuR3XLLINwF4YT/pBBQeVdZhCfyIzi
wweieFamee2nq3DmFoyWx2YYhOl8eFTzGD+H+hqLmWKoVX9RGFQZjN+LfX/2WQ7X
lMfql99m6rwuMDhW/B33sjKOTYoblytIR+ey9g0AAA==
} 64#{
eJzFl01WhDAMgKPPrfYKPFeew40r7+ApXHM0FnOgrNzWtKG/CTSjDFOmdFK+F0IS
0vL++fMMsX1Tf6P+Qf2L+gM8xfmZrl9euLdtjj+YZx7CQX+WZaExzPj4A+95CEeY
otNrr0q0x2nyo4aTGzIe/s+w7bsMArgh4/B+DEJjrcpArZOFnsEYz1Y4jOl9qNjT
xQIhHJpQGM4npwiZwTXpOiEqGzBR2TVMkFyeKULD0J0dRgugCOHxnIzp+jwI+Z7S
h5AGpzHZNU14NAarqPlguhMMxdD1NnYMVl7aYqCNVNJ9FROdlhgEleFLgJgDJRhO
T3DomYVNZp3ZZzCdNUbN+Za5bc7fhClpLgSZ81IY1DHBjH240+7EWGweP7vFh4ZY
nJwbw1w15bzJh5Z30P4u79cES20x1ajimD/Ww0pvX3s1pq/hieGCX2nyvmew1S6s
aRi5NklGrnGVPXFBVNfK8lxpdVXW3IYZ5aqFWc04IedpP5X2Q5s5TzsumeZdrsZd
2YHMwB5mdupYZjbb6YxMc8HgoNgftl5Yvoks31a/90iSufYNAAA=
} 64#{
eJzNVzuO2zAQnQRpZoksr7BIlXOkSZU75BSpfQS2hAtvvRVbg0W2yIGmCrAJEObN
jLQrUVTWQBZIaMuS6afHNx/N0B8+fX9LNr7geI/jI47POF7RG5s/4Pdv136sx8He
dDj4SV+4uL+/x1lnmr2pNT/pS6fw8a6n2ozXNzftuSE38VlMo7/HiP7+AphGF+j5
R5idMMQlZmZjzswp5ZTJpy22Txi9lOQYTjojPUYsD5iYKFG2eY6eJY8YW75R9nF1
i7tyNEf3GJnlAsN5MqjHlFJs/k7hPcb1PGFiC1XXWmAo6sswQkIB16Go5ic9kw7D
KDpgXnmWmNmxvhY4FFOigHMfQ8Zjc7uYYHpWGOgRtYwUA2MM0/FgUV1YKrntZhe+
x7iNaSikPPIzFLOrxd6HOAJsN2wxrpUP/QTHUEka/jrxLPLQP08koXImygwtHSaY
oHwKsAOYnCkl+GGNgTH0cFZMQ4odz8ykS68wFX441lMoAleJ8fS5YZh6/Kpug0Od
h9qQh5SouR6zvcdk6KkFtk16wkBPzieiyrMey7EhTy1w5PFMzHHEo3oQ0oQHMetD
NuIxu2plDlwTnNn7uTI8pzxKVDjDO5tY1HBOrqdW3FBTi31MNZI5WboGpBB4YosD
TM16qw3wiGzXIiVqcg4UWPXEuOWJlCECQUcNquV4HvHECCLkJp4CkOJywAO18C4e
TJgdwDPyDx4vKC2kmCYmerAWFkuqS6Ok6w7WEnmoUKuaIet4TgO7EG6E0m1HKcT1
iAc3Tz6kxsdzHvBYilINVga0vm4xluqIrfIg3VKug7UsRZEYxdINdXzkQzgxIMGU
B+m28SFVTU44CIkKIk/bDoP8xtMC78IDrmeLoRolJUvRB+SYP0ZbHmHWVJeY9njC
xAMvoSaO9cjEo1dhxy6U3kmPEo15EGrXk5QIelA+pOMpyGTl0WIIcdn6aej93FAG
1I0almgQ7bnrmEot6LFwM0pTnGvxjLEyDdtLtYZfKnk1X2J+XU88AV0FTY7q4+Zg
xnjbgDzrfdoIUYi9wzxirP2oK9COZGqWc6daYcgxzZvuvEWJGx7Zx3hbVZfuY6w9
W2iC9Xhvlt7JVxjlQevSxLemO+0IVj5UnqvbyXZtqJ0Pp9xoP5YY36GsMLqNkdt5
X7PF6ByjzMkd+/5Ig77GWCtj65XIDWQHcez1IJN9i4eaYNs1fGxjKm6kMPZ8isk8
pc9Cs8T9PeQC88fxP2KIXgbz3LgIc8l/okv+W/0GSJQzj/YNAAA=
} 64#{
eJzNVzuS5DYMhV1OUCwvrzDlaM/hZCPfwadwPEdgylKgKyBVMZnAB0K0VZ2Yfg9S
94hU7fYEGyx71B/2mwfgAQTUf/719XeJ9Q+uz7i+4Pob1y/yW+y/4vt/P+3XuF7j
T15f9xc+8Obt7Q2v3OnxJ73vL3xwC09/zFSX9evLS3+2/CU/xXT5ARgB4AnGI8In
mNx/Wsw30pDPmBObe865aOO6YI63LkDtGJsxHnVAF6QDYwk0+axhDpfybqvT1pKa
YfuUixMGz56r1EaMy7sTZww/3ojJnf70qz9eJAswcAiYTqIHht/wAzBd/EaH8uSP
76L1rtIdjwJjs893YXdMD6fpM9S6YGiKGBgrKcd2njFaSkRPY8lGDPxxXlVLRNer
aJl48M80rEVjT2FMQp8859SrFIK9ZBjj9n+fZg1JI4bdJDB20XDXWMuyJcC2TWDs
2D7xUJuatVWzbSmUKE8YR0CqRWRptS4bAyoyYeBKVoVsurS2CbzD88xTa/FiKE8V
ZWypQcMBA2mYvdSoY2FNm7GCThhphiy5iZn7UiB5alrrxNNYP6YgIo+nZHXG0Eew
aMEzdETB6wUDTUFhatIaEYCklCZbbLhJqoEPC0k3rInHnQoCY5Rvg5hy5RFZb1oX
kDVAEslmHiRiLUYMclqpE0S8xFWrNF2QdEmlETrrDJpeWtLg0WQICq8zT72B3hbQ
2AYFI8oRo1oLDoItiUXRaLnobcwXgsIhZ+CksKZrL+uIYVB+A09DmqCfqGcwjzzI
jZAHFmmyqftaR58R1Nr55WFLlFtjHeqNTuL7QlQBD/h0rHk0AMQDfQzSUULRy9lR
CdEaj6AllIUheXnEyCH+QumAQfkg8yMm7Unk+SJPsQhq9EcsigHlk/AW7nYWUJ54
WEA16iuxwqqKX3iwqpDMWIWJbe/Ck7QiTVHvstF0nv3hQeGJkAgeaJ6kWR+o3BJO
MM4fwkMlS0YZzpg46nGOkTXj/GljrfLUGRs2+wHQaA+aJ5+RqsbGyr7CIAtbTZ9y
ofTROSFrzknRr1jNU61SV2Wba3iHRKANTzxZdM2+RLvc2MnzPusGDM6O64aIkraC
Tt737TvGgxREnviJ/afmfSo+MGz3RK4d/+50jXMlDNwxMTY48GrXGJmk2SfMAxPj
J0ulMe5wPB2TasQIS5AYTsL6mHgjDxoAjHEAOryaMTFWs7etQCJi6NWEifEcGBrb
MfdJPmJQiTCGuxoP/Y47gklDVDoOOd3bh65MuSBpokPHrOv3O5R3DIjgTzj9mJ4T
5vC5waGVvbxfMXE7hWGBzg0M77Su/uy3ZRwSwDAF/d3RAZMODBK32xrrZ+f5xj3k
GfPd9RNiRJ5i+j3E72Gerg9hPvKb6CO/rf4HC5MFI/YNAAA=
} 64#{
eJytlz2S2zAMhZFMGg4niyvspMo50qTKHXKK1D4CW46LvQJbjZotciBUmUkT5j1Q
ki1RXjmbpS3/iJ+fQAAE5C/ffn0UHz9wfMbxFcd3HO/kg58/Yf7nQzvW4+RPOZ3a
Gx/48Pz8jHeeqf6UWtsbHzyFl09bqW68f3ysR8Me9ZCp8v+MiIm+AeMeOGJMjxl3
7BFj/6RzIwx6bQ9f4ziOpT1mEb1eF18NsxL54DefUNM1U6OEzJHcVGaFXvtQXSin
yQo3UDeM/8oWSxemTba3QCEw5udeZMzmC0/20IVrBsMNwGLUzcAH0Y7hnOhk2Q2G
+qqz8QphO2L89VUMFqK0h+bXjuHSjQFSmw7G6xJ3MjrrtA2sLYx67R/8RA98KEF4
MXWT5+TcxCLkRGvasFvMIHWC7CYzZgmYTm647jEy5IzrhZDDFKyegdA5ea6mKBZL
3ssfG84DEzpJERmDp/X2WhWnkfVACmR+Y0jHQCgnSMTYZHIW3TJiMGjEPDZYeeLA
MrdMDTCoRJhzFt9nPZM0wiBs5xKK/HZ7eh2sHj/lXg6SJOzr2DBAhxdjwXhKsXQ6
MHpM1IH/sA9DGsc1Q+9z8TAnOq6g1zpwF7IHvg1YWchk5p1/YZhusIc6Zeo68PlG
BzuU9lyK3I6OUifjPGMZku9k6+3BvLZCFXKxttdXOq0DTuM8JBbUjU5mtXCtgrqa
B+pYr6MhWUAKlYCQJuvtGREcMEI3Qwf24FfreEWc57VCgpuD20Phrc5T8kuWiLiG
oVQEeRN3zwVJgXXev2ing9PMKhNkItoG9pHu63BrJc/WMMK6XkeZ5cj5GAuS4szF
bxhtOpxn+nDxtWOM9tgohtTIyOhMV2wYxNJlkMy+a0YIdUxFSkEmTlvrDKGemVqT
FxeWmVx2GM9YY5GiK85D3mVAVNY69AgLN5naaiareb7B4CuLLxuBhWv/sExfqrbO
l5Trtf95aDriIOLUmoteMd42Vp3EewvFF2ZqeHM3WnR6ZulqbLSvYrytHjC+ZZdu
7a1R9pml6/MmoGf2fGiyZub88TJhjZGXmKV0eEJd1u53IWvG76Wqbpmmb7nlqt+T
1Uu8qiwxrbHdtU33dhfGdMkfm+7+xla/L8xVkduMhTm+HVV31yFzh44eCU3rOmDc
h2/AHBhzL3PPf6J7/lv9Bas8HtD2DQAA
}
; diamonds:
64#{
eJztl7EOAiEMhtG4qq9wcfI5XJx8B5/Cmddg4lVu8IGcTDphCy7ShP7x0MR45Xpc
uC9taQmBw+m+dlkurHvWI+uZdeFWedzz/+um6Kv4/DjvSyeNP8Zx5F5GUn5cSqWT
JkP82tWmlCyHIVlyG7Ymk1wHJvZhKPRhAsUeDBPBZEIwGUZCtJjsrskQG2Fnk5mn
9GAIYDgiu6ZmDhOQw24MEg8yLyg/aWb+hYk2k7c3g5EVbDBlW2ozZVuazIir2lnN
ZKQy9I6dXjFD+UHyDNULqbuWmfk9hgBGLSrNkFqcmtGL/HMMEg8yLyg/lmAMR0zt
iDBGTontyTOjNy7FYLWQM6DBULRrgTBQvXrV9Gt1R+5EyN3qAeRtVIj2DQAA
} 64#{
eJztlzFuwzAMRZUia9orGJlyjiyZeoecorOvoUlX8ZADZQrASaHoFpHIH1BAWqCD
aNMylGfqm1JA+fh52wWxL/YD+4n9zL4JW+mf+ffL++qtzXKGeV6bcvDNsizclp4s
Z8h5bcpRuviy16GMvU1T9uw6fbhMDq8zFKPLxBiTy2TqYHw9PFKHZsiQCWQZCd8E
Moxk5PFARu8uGakY8O4cpg4Ec9jDFKRVbTRLmEa1ZWycZBmlB+ZHM3BttEOVR7w8
Y6adryeMmnd/reJ515Z+5f81mMEMZjCD+edM8hmKPqNqNQGGVK3W1R0wpKs7YOw2
wejRtfubIV2IAdPk58/2JD+aq84nerqYsi+rB0uASTUD86znHc2X2VlqRBhvAUl+
bHjLkMvEHsZmxDAgI0CPY11MzzdRz7fVHVe+QnD2DQAA
} 64#{
eJztlzFuwzAMRdWia9srGJ1yjiyZeoeeorOvoUlX8dADZQrASaHkRSS/QaKJgaII
bUWJ8kxRFEnbx8/La+ryze3A7cTti9tTeunjM///87Y2KXM/0zyvXTv4y7Is3LeR
2s9U69q1ow3xx4dWZeR5mqon5+ndZWq6nck5l3swVMhlavbtYUUuQzkjhsRUmOGF
SHMs07U7TPfIwIC1s5pREfShYuC6GiKstvZ0NcJqYLPRUyyj7OHfPgNjQ07VLvH8
jBm5XxuM2Hdsj5k6kDvlLvn1YP48QwFGZ6BbNzBjU9nNr98yu9WNHW2O+Cfk58h+
hfbdyoP5n0zxGTKBZxkVwCjGSCUCilXFwJhXDM4dcyMGOUj6RozydNS9c20h9VCE
7BEXbjH9YXKYrFimCmartowMri0ktcPaArTbuQIxJs3BTPYUaR/ewLgSYiLvRJF3
qyuqXC8m9g0AAA==
} 64#{
eJztlzFOxTAMQPMR64crVEycg+VP3IFTMPcannyVDhyICamTv5OGNnb9ayPEgBS3
/qnT913XjuT05fXrnIq8sz6zXljfWE/pvsyPfP/jYVEpYznTOC5DPvhimiYe8wyV
MxEtQz7yFP88aVc7uRsG8uRzeHQZSr9nZgwwEGEwwEAgHnbkMhxRy2B7F+ujQDDl
HaSxYwAaR9VQ+cn/IW0oJrtFbQD8lCnhgWVsTJn9diQM3w+68ZQ8eQyIetn5ieTZ
r9eOMequ47Fl9tehys9t6UxnOtOZzvxvBn1G9KQbzNrbhD9smbVHWj1OM1avVIzZ
c1U8du+W73XINNuqP9yTVGau26GjeCIM+fmpjJfn6mhDjHqt26pNUDLkLKBcL/SZ
GXyGIoy3oJv8HDOOhJjIN1Hk2+oKvccYTPYNAAA=
} 64#{
eJztlztSxDAMQA1DC1whQ8U5tqHiDpyCOtdQ5auk2ANtxYwr4Q/xWrJiadhQLLNK
HK+cN7IseW3n8P716LJ8xvIay1ssH7HcuYfcPsf3x6dSqMz5dvNcqnTFH8uyxDq1
YL4dYqnSlZri44Wb6uR+mlCT0/SsMuguZwDA78Eghl2Y8Bt/fPu2KJAeDROAdCIy
ySxXgPoc3TsbWhXGADSGVoX5LDIhmatMsl47I8qZya2rIaLodjxq/uQ4aQwdlxif
XCtx5oyUL85IeUfLXKXj2hC/y//rxvwLJhiYdlZvMAFAZQA6Q5wJ7WJwAUMWDJkp
CwYMGYud3Xy2xMcUZ0u+THnv5cZcH+N1JnSTqmfq5CT2fMvUPVLa4zgj7ZWMEfdc
5o+8d9NxDZkyhL8+k5h9rkevEeNRjc/KjOKMPyfFUb4qY8n7pmTGq4xCmBmbPyqj
9WRkLN9Elm+rb8X3AoD2DQAA
} 64#{
eJztl7FSwzAMQF2uK/ALOSYmPoKFiX/gK5jzG578Kxn6QUzceRKyVV8rxZbEteW4
XpW4jpwXy5YlN3l9/74PVT6xPGN5w/KBZRO2tX3G+7sHKlzmeoZ5pqoceLEsC9al
BeoZAKgqR2nCnyfZ1Urupgks+ZoeTQbC6UyOycFEhy3syGSyyaAtk4mCybwHYoSt
Mk0QmmDqNEFogqmmQWicwQePOmoaH3Of4X4ujQdjTGtMZq2kRcEM+0lgjQcvbIav
Rdc/hbX8LJjuekmmt+7giB8+r5Gks+TXjfm/TDd+BNOLQ8F041kw3bzgzCC/fs1c
et+4xJg9/nH52bNernUfyo25TibZTI420yJXibHcMkCJ1cZoMd8YNXcO/8DjHMxx
1brO07Tv/A/2ltzehpTx0BMGU97LLP8Qo+4te0bdWxqj7S18IB0hRkXIliGF0S0d
+VlnwMM4xmMznm+iF8e31Q9Nm/Jv9g0AAA==
} 64#{
eJztlzF2wyAMQGlf16ZX8MuUc3TplDvkFJ19DSau4qEHytT3mKiAEBAIpLhOXofK
JgT5W8hCyM/vx+9XFeQT2gHaB7QTtCf1EvQzXP/axYZlDqea59j5A/4sywK917hw
Kudi5w+vgp99baqR52lynJynN5Zx6veM1VpvwhjHMkDwPksYmKxkLL5IMtqgSQzB
wGOWZtJIlwxEojB0HZWMj1Y2lEe3Ml6ZJ0OjxFikjSNdMV07hvfHCnz21pj4VPlD
xrnOMWq9aoZcd+wPLei5emI22V//zN9lyPypGLJuYKZTN5ygbjhBTbiVuXfduIfP
kviI4ixZL9G6kyjN4ClJBrtOMzgEJINDuZ7BS0syOCnW29nMZ0l8RHHurBdWkutu
G+Mtk5wc5Ji9vu3RnChXEzPK+cQM905+A/f3oNWNtt2n5mL8EbVF4o+YYWqLTrdm
pKktNt4xrC0XZlhbLDLQSogPk2RCRnOThdxot8IahhURI/kmknxb/QDHNOZR9g0A
AA==
} 64#{
eJztlz12wyAMgGlf17RX8OvUc2Tp1Dv0FJ19DSZfxUMP1CnvMakCosYSMlJ+nKly
CAZ/FkII2d5/HHahyBeWNyzvWD6xPISn0j/i9e/nWriM5RfGsVb5wJN5nrHOPVB+
AaBW+chd+PcqVTXyOAxgyc/wYjIQrmdSjCYTY5xMBtLdGGlz4lerHmEztpajTMpY
RS2IlmCKWhAtzuCNC0XU4vboDJ9X7jwNxlrElBv/FPGWqWcCyx48sRnuZ9U/mbX8
LBh1vSSjrTs4YpXPa0Ud88+KWVkahk9PZ7ibVIa7+3KGL7/K8MC5XM/NbPb4x+Xn
/nolrRKMFoeCUeNZMOq+4MzK/jqbcecN6tXzhgiK83LLFvPy+NC1Fp41dcWGqk5l
/nPC1czWOYF3JolkJjXKW4aMVJ9NlUk02U4cEtOLZ2K6++L0BF7fXyk2ve0enI7K
N30nIXuiaU+9w2Dye5nln8p088aR6eYNUttZdy06uSzGugvT7qiG8dhsiovxfBN5
vq1+ATh8w+n2DQAA
} 64#{
eJzdlztSxDAMQANDC1whQ8U5aKi4A6egzjVc+SopOBAVM6qEP3ESfWx5w+4ygxLH
K+dFliXFO3l5+74fknyE9hzaa2jvod0Md2l8Cvc/H3KjMqVzmKbcxSP8mOc59HEE
0zkg5i4ecShcnrgpIbfjiJZ8jY8mg8PvGXDOZJxz3mQQ/owBcjdr3Oeg7S34hdmv
PYUCmZYuG5PMItPSZWXCgztDRaNx1hkkTBzcJiNaYdKDqyGqmXa87Q90+eyt+ETW
ijNj1HxxRss7dtQqXVdN/FneryOMui7GaPFhjBpnxqj5okwl7yczl67nS/i8RgSI
RpkS2bVIUTLsppovNomed8X14wwNpcrQJBy3Y/kDhGnVfJmqVfO0Yww10K556pbO
SO1/1LzUTttbevaorr2uKudkvM0Ar1+FKdFqrAuc3AWQxacwrTgXppmv7R+4nndw
YlTWhl+MX6GeoctnZ/ucY2rUMyyTbQivZ5A7DvJ6dnLXRl7PLs9Vl2szYtOWDA2M
zpjSxfR8E/V8W/0A+lG2V/YNAAA=
} 64#{
eJzVlztSxDAMQANDyecKma04Bw0Vd+AU1LmGKl8lBQfaihlVRnbirPWJrR1YdnDW
m8h+UWRJdpyXt6+HIZcPqs9UX6m+U70Z7nL7RP2fj0vlZcq/YZqWUzroYp5nOqeW
mH9DjMspHamJ/g5SlSq34xh75Tg+dZk4/JwBiNBhECB0mYDXY5B3mwyEmsmSZGic
tZosSQagVrRIgqEbK0WrJHxoMiIWqfH0MCYVJt+4KeJSV0+IPXvoos8kbdo/WEuJ
Nfy8kuVRYMRLoUbc+SOFPZbpYlw7DPPPaXTAE0kxPAg209ODzB40mc2xsEmSwRKg
whoM76yt0nHnj7QZbjpjLp3Ppj1aOosx57tkUOSW5Wdr/VGMsY4pZrf8c8YzdocP
PbFwxPS38ucqOd+Yg5657FkTXGuLZ41yrXWGC0yGO+9MPXUTajWJQegzRbf5bloY
LDda7zjBoIlyBqx3LrennasIqlXnRliVXz6faT+Vt1Ute2jHtW6rmkyE2J7vhUEQ
sY2aaa0/G9NYxzZmt/w5ozJYMygni2Ka3X7G803U/7a6H78BawWaX/YNAAA=
} 64#{
eJzNlzGu1EAMhg2iiUYwV3ii4hw0VNyBU1DvEaYdrVCu4Daa5hVc4lVcwRVShFD4
bU+yySS7byWeBJPNy77Jny8e27GzHz//fEs2vmL/gP0T9i/YX9Ebmz/h/Pd3vm/H
yT50OvlBN3x5fHzEUWcm+9A0+UE3ncKf9y1qN14/PEzPDXmIz2om+nvNmPsX0Uz5
Dnv+keZKGOJaU2nSjX3WTx6jT2dsK02vl/Su6dN0pBmzgZSep3GeznmtyerSqcs2
HCPRHL1ofuf8bWW8rySuD3F6wtU/9Epczq4RvqXhi6a/aEYzYwpEQtDwrNGVzBo3
tQ8UsZkmmuYJ8JYT7ZxqSDW2ksaelSbgnnbpDY0UxGvLcf+EKDRrqAxbe9zPFOra
2dzZrMviJSWIc4o9w8PWPxZ3qefAKXHnZ4+PFO5h8hiI9aYybDRd9GzsctK9hCOO
GRkieWZYmu446tiIaJFLhFI84FAhuNazR6hD5HYcSiVa1E1CCTlELSdjNlAiwSDF
YLScFBgRSslvppi04wSEkCX5UEyHWzacgmWxDDYcwzI1HKRfIVlhwp6DVOCqqRgk
fsspHKtGMYFCl3b2cITmYk0JncSWQzPHMMk81vpHrZbZmjPM6XfxQn65RhcUzsZp
/Zz18SO1ZqBrnDFoiBST4YNwzFEzNODDubOAHXAmzCZ1b+akobrGOeOcYjT66p8j
zllBwAzqB/j5kANQUEwSJAFf5cAo0+i3odG4PeQY1XCbz5HgHzZMh7AywolEaDgE
P4cFoxykU8MRYl6sgcYyruXoghZMksBxZ4/IxRq1B0kQdvbIGpNQ6vEwtf4ht8af
wPpA7p53w5A/yynQPlfJSkBaakKI6q1d/THMXFvgLI4HdUyLxlyjUNHiUR2z/ua1
DpXoSj0MJXDOqiOf7vYcLbmdwKGpWP/yjtdwULsVj0Cwt/KLpldOPcfoJfIrsLfy
RWNtg91Ib5JqsbXyRaPtpyvaZsU52sW8Vc0ab2Pw2cyJqvGWN2u8HbKEeF3jHNaF
XjS1BW/sodqssXZ9/OZW3qzLfAaNWNRbjrVD15CvHUvc2nPx86TV4oYmVk19DWv8
A48u/dRNzXpq4+dR/6vRn8Yen1Gv2MQLc1Uj9X2tT97KjzTdmOurn996nRvQXH2H
XGlujv9Ro45+Cc1z4y7NPb+J7vlt9QeRdPNP9g0AAA==
} 64#{
eJzNlztuXDkQRWuMSQjCri0IE806nDjyHmYVjrUEpoQw4BYqNZgo8CYceQuMBujA
oO8tvu5pVttuBQ5MqX/vHV3Wn6237/97Lb4+4PE3Hu/w+AePP+RPv/6I+5/erMe+
Hv1XHh/XC3/w5vn5Ga+8Mv1X5lwv/OElPP0VpW7Wq4eHeW+NB73LTPkFTK3tHnOq
td5l2vxtmR+kQa+Z/9WGqeqouWPpDdP8XbaBVbP0bjMyp7oYUZlLh8xVDHVFnVvJ
mLp0FJevcqHza63/ugyuDE3SnXHxC/MZql/IFKqlYtmZdstgqwKDdJS89qInZ4bG
wbxhqciQ5RgZbnZmHKktSypzcLPD5s8QDzpZqjNKg+C7exLsORhEEQbdxOeKwTUZ
VQy52HX8E0yuxbM2U+Femz0rzmQyXJ8JoZbo18oXXc8KewqwMkN8Vt7BKLaYgqdR
9SiZvX4QZjAiyIMwUpHhOzCIcu98VqkamJFNWYR03IfKQGJ3Bltkgkwo9FjLQouu
GGSadccKG7IAWJZ2xlikk7Eh4CQd2Bg4MmkT7/mCTN4ZFaNrRF2EZWZ+7YrB36OM
xWz6XgBRtTnoIKg0IftQlYxIWwp7UdzN7Jy2A0iCUNAZbJthBRBWNy1iKegoAy1W
2HxMxgnmxL1gTRfa6TrWCvaKfg2moyt1aPIJBS3Br7msaOw9aVlbKRwyuw53y4lG
ZDDUMYlx9qBYlYtO1qAjlLFiT7Us+nTKEnT8Rho59Y/sV+pY1HGZ1nIi0T9CJ8t3
dHI6Db8h9amYy9zEEO7AVvedA8Floo4V6Kj7zhhTZsa8W7notNLNrdkY/2u3B/2B
+HBmcMDuNY8YN4YWQWSc0a03MaQRQ3xy4y2qBLUWmCQmTdwKKBS0R/ZS2HQK9ur+
rQC1Jmwhz8/GQIglRm9gORjcD/FhAcrqcnaW8VwRiX6hm89NzBHEjrToV9ZLF8tg
Aw3NFnKxOn2NBEUh07GdSS7j1UgSU4ajZs97KgvwK/CPXvToe/WNJh+AOFpp0553
zD4OShwSDIEdd0LvVPrMHuUIsrlW6EGeW527dRymZNrGNJ+9GKLCcwlHDxk/bS8M
P+E+hBINRm+Q8aP8wvD4wejFWB+8MiqZdZSfGf+kHG2IEQd15RxcR96Z8eOQI1zK
GDwnv8MsHexlPEMO5jiCd3smopeYbTBI53GUB7/QMzAIx7+7HnVWfNh7xcus0PXd
HjxVZzih6LuP98icFgOD1E83Mnt8PKJuc+YJ6hU2Q5wv8emdedVj4m758q9lfgy6
YwOJ9zvthsmHjuqleCLzo++Q18xP12/IMIZ3mOOL6s+Zu+tFzEv+J3rJ/1bfAGnk
dy/2DQAA
} 64#{
eJytl0tuFDEQhgvExjJQV4hYcQ42rLgDp2A9R/DWGqG+greRF2TBJbLKFWqFNFKQ
+atsd7e7h3QQ8aRnJu4v9a5y59OXX+/I1jdcH3F9xvUV1yt6Y/sn3P/5vl7jOtkP
nU71Q1/4cnd3h0/dKfZDpdQPfekW3j5sRe3W65ubcrTkhg+ZQv/PxHiJ0wswJeJ1
xFymYwbajpnLP8n5Sxp4bY++S7aV8MpNOq/9Iv3ijSBPPmFrUuYyLYxjSRCkRMRi
sxXMOoZgMotXotoBW6drTHGV0ML6HeP3amZZ/GIYobcFG1LKPTQ+jAxPkxjDzIXW
zLRm4BvbEiFYrAuMmt4ZVscqU8pjMSRODHkPoz2dQVyaHDN98GtmpNuj7MCILfz2
SJ3ZyEGcK8Pr+KzscUyzKmIpPc5rv5xGrqvSMLZ8reMzM7ahYM37Os4rOaKVoguh
vM5YIkS1UYvDwJRmsu6K/cWOkbmGLYjK08DoPY20T5XyZLrH2kC2cF8yVXHJ5tmo
i3AzM4pdEOWIskdB8sYvTjI3lQshUfQUeFurRL71l3MhMaUYA++YXj/Q44KHOkAD
UyC9tz55SMqZrM9WjGC7NoY2LIwmhTa6og+ezaRExgByI+PCrU9JNIQ+KQM/cxoZ
iMmw2rsYVEgi5MbnvV8EZWjngG+QANEj43y4hRbumUj4Fp3fMapCICcGGMQQc05b
Xc5HNUiNNpOjy2MMXXRUGSsMr1k7346+646ZUhlLbQ6bGOKPAmGQWYDQkO7sek0u
9RMi7PSabtiMWHqXzqMuj4JAXpPJqabvmQQxlYEgxNLKddTlM2n0NZ2kjCQPMZv6
QbbVZI/CVmWSsjPRQ5wVgg4mK1gog5gNE3ytG251JgliZBNnhRK6oRcs6pVHRgtc
yxwp4DaBd36xQtYxrhqUM+2YQgHSKYW4nHDyY2QweLSrSJ3RtscAED/Wjw4eqbHV
OKGzs86RTX/p+NLCUMiGTNnlvfrSa7IdYm7jl41LO7zaoC47pg62NjD7oN4w0uYt
TF+G+YYpdWiLbV1hJmNs+NvkvSJHb/bD2s6C+WxZmHs7XOthJMT9GMNhNTN2k+uh
Jm8XVX7FXOoBXJkWB1Xld3Lmw7ozOq5ptKcd1o8LY2N28KvJEZLOcJmmTXzqQSyW
rVr0A1NWjNVJO4V2tdrjY2NFp/faL3sM6nGuxBmddpWxfOk4BIHntiHO9ljW8i71
ic26WfO/MKBad/r65GcnQi+ZysQnniF7vqby9FLmAHmunOlIUPPrgLEYvgBzYMxz
mef8T/Sc/63+AJ3HlmT2DQAA
}
; hearts:
64#{
eJzt1zEOwiAUBmA0ruoVGifP4eLkHTyFM9dg4iodPJCTCRM+3utiX8NPkNaY+FrE
4BdoQSg9XZ5bw3GjdKR0pnSltDIbLrf0+30n6T0sn8ZaydJBX/q+pzyVRD5NjJKl
IxXRx2FclYp110UUj24PTTQNjG9jgmtjXPAtDAkHjXPQEHEeGW4uawJVQo19bIZY
yDgP750uGl5z6uelTCwYr1QR7h+qBpoQFxuLv5nPeGx46QIm/fOAkSUnb2TJyRqe
CS5vmIwqqjDS1KixLxvVixP3FdC9D8tt1kzF3/yeCQVGTV5tgpq82ujJq0zQk3dG
46GR5zwwMNqZqZlZY4aNGTBowGTcYR/KhjNvZOPawOinUZ3RT75KA6LIlLwTlbxb
vQADV0S89g0AAA==
} 64#{
eJztlztSxDAMQA2zLewVMlScg4aKO3AK6lxDla+SYg9ExYwqIdsDG30YaXaXzkoU
Z5wX2bKVkfLy9vVQunywPrO+sr6z3pVD71/5+elxqJS1n2VdR9MOvtm2jdvWQ/0s
RKNpR+viy5M2ZeR+WSiSz+UYMlSuZxAgZACghgxhgonnwyMl5uwwyG8CSEOaYUcQ
+ghnQ4rBwfwaArK+t+cgGOu7YZw1vITp4vgFe78uZVAOBdXbd2GGV51fMMx+BYdV
Z0/lTvgMAoUM1d19JlYzMe/GjzFEN/kGJzOZyUxmMv/EYIJRqdFjUKVhj9E51lgt
R1T5HI1Vy+jM7TAIZnqNqaIoMiVAn3PVmRgMI+RG9cZftY0sMK5gWl0WMVUyZhX7
vku/0PNr36Vr0R/G7rRlokDsDIYMZBgbVYbJxHwoKSbzT5T5t/oGyDsmgPYNAAA=
} 64#{
eJztl0FOxDAMRQNiC3OFihXnYMOKO3AK1r2GV7lKFxyIFZJXxkmESOxfHDEjRkjj
NpPKfeM6qe2kj88ft6nKq7YHbU/aXrRdpZuqX/X+211ro6z1TOvaunLoxbZt2heN
1DOJtK4cRaU/99aUk+tlkUjel0PISDqeIaJ8CoYzh4xQ7I8aChkm8gyrj0X/9SjE
qJLLWDp3LMONoYihgfFjdwyYQ8ugcVlGgD/SkHw0w+OjKKP3PpjRWdc/OKafwWYV
vNPemz2GSUJGcnc9FasoNpx3eSJW5SQ5eGHOy+SYYYoZm12AYZvJgPFpGuUpZE5U
E2bqz18zbhaDOg8Zs15ABsmF+X8MTzAueT3DLnk9Y/PJWU0HNjGOaotlUG0xDKwt
bX3uzKDaUtf53syZawvbzQNgxk3IDlM3kz8zYhhcW76ZvdrC424G1paZ+JEAafGc
Q4YiQyBWf8uEMsXMfBPNfFt9Aow0CNT2DQAA
} 64#{
eJztlzGSwyAMRUlm22yu4Em150iTKnfYU6T2NVRxFRd7oK0yo0orYACB8aCdOB2y
CfnKQ8aYjOTr/Xky3h7cvrjduH1zO5gP75/595/P0Eqb/WnmOXTu4C/LsnDvPORP
QxQ6dzgXf1zqUCs7ThP17Hc6dxkyrzNoFQxoGKtgQDEfDtRleEaJcVd2gQvBlwLB
sGCPLUXJYHBDJYr18SMkA2oGYB/Gm7wvkPeVxf8YlNGTgPJZyJFR8HrLdSbMYZKo
noUbm8JEsWIwh4lixZAIE4Wf1g77kBT7mZB2+Q8OZjCDGcxg3sSggpE5aYNBgC6T
cmQRzkoGY65t5LgV08iVNdPKuZmxIZU3cneesw2pvFUDRCaP3KHe6NU2GKuzFxkC
RR0Fm7Vfuc7QqiErplmLZoYU+8f2Gc0+JA2DCkYx566pGM07kebd6g9PV+vB9g0A
AA==
} 64#{
eJztlztuwzAMQN2iYz9XMDr1HF069Q49RWZfg5OukqEH6lSAE0tJsUWKsqm0BoIC
oS0rpJ8ZWpZM+vX9+2FIcuD2wu2N2we3m+Eu2Sc+//mYm5Yp7cM05S5u/ON4PHIf
LZT2gSh3cYsmPjzXrozcjiN58jU+uQwNf2cAIOzBEOEuDJ4dD7ICUCvJsDB8RXKs
FcVgNkOlgIw5mkAy0M/ImFcYjAeHofq+QN5XUc5jUHpfFAjqWcgrZ4XHm9HCYHGz
KMmffKbCzawYBoubWTEMCTez0jNX1TivSQzLY5B2WYNX5qIMdjByxq4wCOAyAMZR
zSCAcVQzahG3GVQvjN8zAI0/uywTPAYzQ1tMzBLojGFTrsz/Y+wKt4xd4YZprHDD
LHNTuQuSWVZ4I8cZppEra6aVcwsTcipv5O4Sc8ipvFUDzEy5cod64/TiWatt8FQM
bTHJq8fEuqyXQcmgYlLdulJDaqZdi6qYtyQzm0iO2ZFepi8el3Gli+n5JvK/re7H
H4xdySf2DQAA
} 64#{
eJztlz1OxTAMgAtiBa5QMXEOFibuwCmYew1PuUoHDsSE5Ck4DmntJI2NeOJHem7z
8px+dZrEdtqHp/frieWFyj2VRyrPVC6mK25f6PrrTS5aFj6nZclVOujPuq5Up5bI
5xRjrtKRmujnrjbVyOU8R0ve5luTidP3GYTgYMDRFxkyGTQZ6stkQDFpBOkBtQKq
L7qDDWtFMZiboVIaBiQDfkY+8wGj5vmAYZHjAjmuXfkag9L6pkBQayHvLArNN6E7
g7uZTWF7ck2FmaI0DO5mitIwUZgpCjj8x+XPjriIGE8Sg2fml5ggL4Qu0/HDhun4
c8304qJmevFVMf041cyJcoIn//w0g5LBljnI8/Uc9vYLzQzkzPw/Bh2MjOEDBgFM
ZoskZS5IBot3D3LLxgxyS2FGuSXvz0Zu4X3+D+UW/HwZGjFs1WLSe5mXGeQWZozc
kplxbgEwnCwzQyT3ZQivqc14/Dl6GMfz2Iznm8jzbfUBqcKs2fYNAAA=
} 64#{
eJztlztSxDAMhgNDC1whQ8U5aKi4A6egzjX+yldJwYGomFFlZHviyLYSmd2dLAVK
vI7sb+WXLCcvb9/3Q5QPTs+cXjm9c7oZ7mL5xPWfDymVMsV7mKaUhYsf5nnmPJT4
eA/epyxcoYh/nmpTjdyOo7fka3w0GT+czwBwF2E8mQwTZn96GG5MMMSdAxqlYHgE
FAZRKZKhVIxaCR2SDCSD4xlfjQtyXKvyO4ak9azAFesu/7koPN+MrgytZrIS7ck1
FWYWpWFoNbMoDeOFmUXp8VUK3TJ9zNkM+YvswX/mSoyTFU5lFD9sGMWfa0bbFzWj
7a+K0fdpyVwoJvTEn6MZkgy1zFacr+ZQPS8KZkdahjoY6SEbDAEmAzSGaoakU2ww
xYLrDBWLcDoDKI0dzVDBOJURRZQYXzPFCvEzKXOYbRdVTjJ5hXZiS2Z2YsvCELZj
SzqfjdgSz/m/FFtwBYawHVviSyn2Y0tkjNhCedZ1ifPTRIGTGFiNSV89kzGli+n5
Jur5tvoBfRyUSfYNAAA=
} 64#{
eJzNlztSxDAMhgNDC1whs9Weg4aKO3AK6lxDla+SggNtxYwqI9s4keKHtEMIq8Tx
SvlW8eOPk7y8fT0O0T6onKm8Unmncjc8xPhE5z+fUpE2xX2YplSFjX7M80x1iPi4
D96nKmwhRIfTNlVh9+PoNbuMzyrjh98zCKAyAOBUxuNhjGwzUuNCQDqyzeRhCEhH
XAtTGDZOwQBnwM7w9jQY0a8GE433C3i/Vuc6Bnn2xQEn5p3/Mzs03oSuDK5pFifm
43PK0mSnYHBNk52C8SxNdixatWjeh2ZpDPpd7sHrmbrmJVPXvGAami8YTYdHMn+s
eclUNb9laprfMjXNF0xF8wVT03zBtOy/GMdPuCpj6bthDC1zYZhTizZ20uGt3V8h
jNBfEyxri2mN6tieDBoYrq0GgwAqs8ywSOc4g3nUO5pfmI7mM9PTfHo+K5qPz/kb
0jz+vAz1mJhVY8J7mZXpaD4yiuYhvw51NG/ST7zWQQwooje2WTUTY/kmsnxbfQMa
WW519g0AAA==
} 64#{
eJzNlztSwzAQQA1DC1zBQ8U5aKi4A6eg9hFot9JVXHAgKma2EivJknb1W2XIJCiW
nVVeNvo82/HL28/94ssH1Weqr1Tfqd4sd759o8+/HkKVZfPbsm3h4F70Zt93OroW
67fF2nBwL9dEu6cyVVVu19Vq5Xt9VBm7/J1BAJUBAKMyFq/EIHXODUIGss8UoWuQ
AfCxY2iGIvAxZ4AzIfCJFQbL32owdobh/bGh1TSC0xjk2VMARqw7/2YMaL4JzQzm
NCnw+fiasjQxqBjMaWJQMZalicGMqzPO+0XVGLRnOQdPZ9rOS6btvGA6zleM5uEl
mawwWt354zQfOY/HDhpzGDVP+2xu6TymbNnc0vnUK3YCFM4jpG7ZFuM0Tww/AQp/
QI76uowJMxiYdp+PsZvR2I+588J35lBMf2ctRF/bayp06LghBq152GdmnL80g5zB
mpm5tkxdowblnAxOMIW+LQaZTj0mrbBIZziDcdYb97iKadwrS2bkfLg/K877+/w/
ch5zdwYMTPhMiVQGjeY8xov3wHmIf4cGzvv/raNyaaa8ZjcY/NSdV8sUM/NMNPNs
9QvETVBI9g0AAA==
} 64#{
eJzFlztSxDAMQANDC1xhh4pz0FBxB05BnWuo0lW24EBUzKgS/uSj3yYClsVZryPl
xbEs2UqeXj5vh1beSn0s9bnU11KvhpumH8v197tedRnbbxjH3tSjnByPx9JWDbff
wNybelRV+XuwXblyfTjwXvk43O8yPPyeAWDYYQgAdxmk/2IIsFqhBcMAQLVCC5qh
rgYteAYkA2lGzWHMaF+cYFqRdoG0axW+x5DsfRHKkFH4ot/ZjMFJqKcg/UWLsb2F
uT/p095NY5D7aBxDMA+2ego4YhgFwzgPEm38rMwySLcuLMP11DCknFAVvM+wZ9g8
KmTId+NjfpodsQB8zNMEkvc7wRzznVsXAAdxSCfjMBOr52L+OOY1w/JOLZiYDwUT
86FgYj4UTMyHwhlywZ8xKC9gyGRsT8xhxhcJn2Zi40xxeMm1s6q313KfuNN7QmZv
Se1Rcoa16id7JimDBEMJRm79EDysMCTuO8UsOvS5aWIWa4Mc55ggV1qmOYh0zl0Z
7KkcfO5ex4w9lYuJUcKFY768T02ht8XMxm4ynGdIMuSZ+B3SMOG7qGE2ysUZt+o8
Q3bROWbzcp7JfBNlvq2+AMWyKIX2DQAA
} 64#{
eJzNlz2OHDcQhcuCE6Ih8woLRz6HEkW6g0/heI/AlBAWvAJTg8kGvsRGe4WKBBDW
ovwe2d3TTc5qB7AAiTM9P93fvC4W64fz4dOX99LGXzj+wPERx584fpFf2/l7XP/n
t36cx317yv19f+MDHx4fH/HOM9aeYtbf+OApvPw+Sk3j3d2dvTX0zr/JmPx/psb0
XRiLN9jzg5hXlsEfmU3N1VhrSjFF309HPHYmPthLTJo6UwN/rmemxvj8FKM6q2bJ
IKPZHJh4YfA58ZDYh7elMXT0gYmN2a3tjB1s3hhFvGQM0+JFXmHUr0y2lHx9eLnY
89SZZwW0YHBOYGKEQRvz0pAHw72sM9m8993GgfE70+w5Mfa0mbwyfV48+7wzlYyp
wI4DU48+NHo0YeI405nNz+nA4Cdmu804L775+bDu+Ekig+T9F0jJjALPtdsZCb7a
qtOcWLyNa+FawIhvNYDIMjMaKFCwCCZRcpErOhqEU1mKIDrCsuz2nHUUOiU7IrnV
oZHhu9cl8/JSSnFVJsbDWnHKyxxSqTTqIMLFRRUSmQjWYNRRcwEQfy+uIRYnHbHa
II6OjLGqKqlBlNmQPDBeXYPgg9CRpa3H8V6quUHOu44U+ODE0MmlQRKCazf6nGVg
oEMnmwUHw6CSXRlsbjqAIoSQ8oFuhNCkw0WCRsCTnyg06XCllRFee5xAaNZRvDrM
PQo/FXHzvDJOI3UQiwrfQFY+X9EpWHuNQRkk+KruBuaKzeO9bGT8Mtg86xjz+zT3
SYexMfpw1tGwnNdi1rHlb2kL39a0IqxnHaQmgvAQG7MOEnMZYgw6/szkHmHHWIWR
Z6aIG2I+iNpgz4q01ImE4MfzvdyG9BQkhKwcGHoX+b6lcmx7mIFBtp9LAjLOj4yp
q0Rwr4IyRnMnHZZUXGaPC6xiqFjBZqaUpbXBUIROlisMixhLHRO9xck1ZmFdRiXX
nqAox1d0MFiWW/OphrJ+YdJmT+7NhCWVncFf7sW24VhYpTecptM7zM6w/Tj2xt64
4D3bOtXGtDbm2BvX5iZfZe14O9Pa4ZHhzXqT3pl1e4D82plTB94ZLJ/uzFebGbR5
n9A2V5tVbdsR7DZzuwBG87rTkNUcyp9jlfZ0xl5h+rz2jc3ExLgxrl8K+47p4MPN
zwh8hD5ydd15XfycKufEKhXbdq2msO7gzkz74rDnIxNrv3ZYU6POa3vIA/PN8TMy
LXS/A/PWuIm55T/RLf+t/gNy5vcz9g0AAA==
} 64#{
eJzNlzGO5DYQRcuGE6Js8wqDjTbwKZxstHfwKRzPEZgSwkBXYCowmAl8iY3mChUt
0DAG9P9FqbtF9Wxv4GDVrelu6emzWFWs4vz5+etv4sffOD/i/ITzL5w/yS9+/RH3
//m9n/vj0d/y+Ng/+MKXl5cXfPJK87e01j/44iX8+TBKHY6fHx7avcMe4l2myf/A
5DzfY04557vM3H5Y5p0wxGtmU7NaF4lmtl7eM/mpvcFfWsDgkBsMnPX6Bf5S6jSx
fj1e+zDi+8zTqGMtalzlL7Egk52pWWCMqaw6rd1gkl8KZbPnyOiSYIxJKPE83Y35
0plXzfAKXmFy5vT0drH5zZGnZpDh82GSLn/K88jA485oLpsJZ6b5YHMzzvw9ht7K
lImYuWwMn3y9xGuVkT4ZXWj03s+eui5jpXHyndn5GSd/QcZqbABDZ+joK+ZEBPFU
WKJSOtPmts8flylSRYo8axz93DyS+BVVpFbRWA6MVQ7BkU6FWfo8MfKDTqBD/K5i
rBpLpe2DDmyEh010QVJHyBQZmKgF0TaDXVrTqUIG5J7BU7VkSFm3GZNTkT0DC7WG
5FnKm1Cj0J5hBAQQFAsDq3TSYDNuGEZLDUyEo+SoA1uT5SkkGkTLoVLK0c8yZZjN
ZSNAjzpYdkh0Mlh+lgt0VEd7MH0LCwbDpTCVW/YgPXEvOyOhwiZObM8EBAfPg0H+
hEJm1JGc+soLcLdMZcLE6qATcsbig4gnWEYSStCDTuaMcqrQKVNRDJwGPyugKIrC
onlStzlk3esgnqg7qouCgVmVk0y6H0vce1JTqUVLnjAWTBx0apCoaYERyDaBqJu4
08HDGCuhitUSUIToiaNOpWsXzj3rhIGxDOKgI+o2MwWzcpIMz0GHcfR7YBakCuO8
01GuFVtSc+8tmcXebuiYJc8wMBPCZ8MaZKJDJ3qmcrCE1I037ZGe8QgbElFKG/2M
UgGIDDyzFquDTuVehCuQaYB4mg7+4U1IVfcT8gmjIcWGdUqD6mKIuHkfZH0ZGF8r
M3ROlXseSOlUxhqF288+WixeOm3JY41CvdDZq9zyL6ti8+I3MA1FrtsMK3zHNfoQ
f1FJxRPMq8yuJPcyTSZhNWAIlnF2/x3Dck8mu/N1lUFnuGLYNpxBUFnn2Vh6hzkz
3n6c6cspsj/1TnVmvI3hh6EbGWv7KpPzhfF2uDL0S1tl8OS7DBvL1oEvzKkzmlcm
HRhv8/yxrjy2/21HMPq5YQH6NgLz23YWV0zXscVJVLF2ZE4r41lq4Raz+plRxbKz
UJyZr5nNz77VQDwDyuC28zoz82pPM9+shVLPO7gj07wPQsf3UNe50bd37+whr5lv
Hj8g4xn3bcZjcY+5e3wX8z3/E/1x93+rXx/+A4IDtCr2DQAA
} 64#{
eJytlz9u3DoQxicPaQjihVcwUuUcaV717pBTpPYR2BILQ1dgK7BxkUuk8hWmCqDC
YL5vKGlF2hs5SKTVaq397cxw/tKf///xr9jxFdcnXP/h+oLrnby35/f4/tuHdvXH
vb3k/r7deOLD4+Mj7nxS7SW1thtPPsLbx1HUi+Ofu7t6duhdOGWq/DmT0pKmv8DU
hPOMWaZzBtrOmeW35NwIQzjaw3ctpfh2bsLDcV327iWvZ00P9TlNNSxTx2hQn4sd
YUnp6TssCUcf4t3n6ktJOCIMTROvgREwLiUzlUwypi1oZaApt5VA6w3Gg6kBR88s
D89Xm71IESCQhNf3xjz19mhdGeX5bMgD15WOjJZguvDpFsPl7Ewz6Kma0qedSQmy
dmYhg8e87zZz3YrDVgqSOixeuG1xjzggRdsvAgWZa5E+OwMXp+YerI4FTDEM0XTw
M8IUmjJbQF32dNgYNznYUw3CS6LUF4wkmipVaTg0r5HrGZHYbCGiSarjNwOTY4Qh
BcEI6sA7pMLAZIkzZJQc8BFigsPyQscg7M5H3Ev2XuByjaqXPOjyzuWqyFbJVKUR
67j0jJ8l4hEYqIpUBSelnil4AhNYYzBHNQWs3/X2zFEcHQ1BDj93UVOU3h7PisBy
NEJZDMBclLm3xzsIIiSFy4c2kUtJgy4rrbQ1Ayy0xDIPNpsgaSVjvoDDysDMaRci
DoJ9mUfGN0GVeW3KX5FDQTNzei2VV+Q4CmLSILu4JjGbh3hFLBouCRaxahQ+9HIi
BME1Ck95YdtDF8lu0JX5Q0beeeYR4zbG1BdrPy07sITEB3HwT74YRIMjqxYrH+Ne
XGktV0JCCoqbEwSNjPjWlh1SjMrgnheMyVnW/i4s7sE/JsfqzqJq4Z19zzizOegW
1NgE9cylMAhIV0syJQRBQ66iHJiiYslaCbmxdlgxymRPdKKyScioi4WCYsDvWWKo
L0YwjzFFq0B9JSYAfu9jdKN/2MWi9aHcsmN2zRfHeEW2AchDklKOXqC+Xxf7CFU5
Fqcwj1we6xTzC+ZUq0INGGXQHIeYss85ilE2u8CnzOyRYU9EE2RrhcloYWwSI4NM
Z+dYGzDlphfMwnKwCW39l7P3ykyNYZ1rMFswFDgcrvZwbITapkjrpBxSbcpsDBtF
S66JDJ6oGS7X3mtjLKxTzaytNqTcwR4bh6HaKJ52Zh2eK9MG+Tplrwx8rbcYjm7O
cCTPkcGY36d+2wMYg7DvNnO7ELbdgw1UswfbmH6m9IzVT65SbzBWXhz2zNTenm1X
pCQvyEQthznYmG135W2zlYvtwnfGtmVh3aXptm1rqXxllmn9w1puO1FjHZN+sYfc
YjrVXx9kTpC3ypnOBK3rOmHMh3+BOTHmrcxb/id6y/9WPwHf55TR9g0AAA==
}

; spades:

64#{
eJy9lztSxDAMhgVDC7pChopz0FBxB05BnaOl4ECqaI0eTtavWGYwcWLHE3/zS7Zs
bfb1/fsRtHxyfeH6xvWD6x086PuVx7+erOZl1RvW1R5ycWfbNn7Km6A3hGAPueQV
N8+lVFXulyV4hRZ0mQATGJzDEMxhgHAGwwS4jMWvy0i40GPMXI8hEYG/M7vFixga
YMCPF/nrHAbWWeLlrc8shveYF9MwbX0O7j8YnS04OrbN+wxVMr9mzEpskoG0G0+c
1hS69WxGIOlk71bMbogg3GyWDJImceLLuhXDA6JvZqTFw1jC6DAeDdAZo6aow+jE
udj024wqHM0ZY57I45xJ7xN/RIjdwbhQjTU0GyS39lvrLO9pv9tMdEh1bAIVQzEC
5k/iTsYgJPPC1t5QY+JH9L21x0xoX+5EptjPMkY6K4Lmfo5CiIVMfnaiUCGTM4dH
mUxxBlWglCmYKJTLlGfZtjsV6jkTI56f+CqPZNFsM+Zz6DKh8qaRW0/yT7lGlTRa
vk+FQsVQmUirbNxgqjLG5PnxhCFHZui3R8apj1z8+6Wpvf/FNcToV6Jja1osIJAf
L/TjNcJM+eYYjftle2PkP9HIf6sfYx2h1vYNAAA=
} 64#{
eJztlzF2wyAMQNW+rg1X8MuUc3Tp1DvkFJ19NA85kKauqhBNAkggUmcEG5MnPgoS
epb88fXzDtK+uZ+4f3I/c3+BN5GvPH85pF62VW5Y1zTEi39s28ZjlJDcQJSGeEUR
P461KtVel4W8hktwGYL9DIoNfYa3HFyGcIDx9yPT7p5NBpUizZQStGwXj9wXkGW7
eCRjDNsRIFdk+lCiIXQZTCHTs2KI+Qu98BCDA4zhZ55HzBgrxhACUrbnRozlkuTR
/nk1mHqByxBb4MZzI8YeZ67cZCYzmclMZpx50jta3vd9BnWS9RjUC7QeM+tBkT/L
XHllsMyfRc69aS7rC7sGyCqnf9UJBrOnbqmLovqvFGOdTGQoP1OtJpV2OWOeV6o3
m1aQFWNYI8nP/fAZKn5l2o3VyvYGoz2iGMMjxn6cNsSMfBONfFv9Aj95nK32DQAA
} 64#{
eJztl01SwzAMhQXTLfgKGVY9BxtW3IFTsM7RsuBAWnVrFBkYWX6OXJqZdlEnrjvK
F/lPfnZe309PpOlT8lHym+QPyQ90UPssz7+eS67TrDfNcynWS/4syyLlasl6U86l
WK/VJD8v3lWTHqcpR4mnFDKZLmekOWkPhhOHTKa4PeIoZFjHvWG4qgozlYUay5/3
gNERMQzoOxNZR3AMNRqSfaWpi0vIbPRijPkJvXQWwwMMtfMlz5kNg/uVOJs2d2LM
WsqIbs9Xh/EvhEyWHgzEcxyrQ8wvd2euwLBfOaFuIIuPZ8T4ddFhzl+n/2T20hav
Y5Dh1k2kq5jZZ76y2y86TJvuzG0xO2m06v02gyI4YGCsej8wVqnaP7G2cL1/Ym2x
W3VPE8xKuAVtYXdwQtriD05IW/RcZh1BbbFMT1uq2IDz5cYeagvw3tYVIGW+BuI5
cuTH8AImTEPMyDfRyLfVN3+MWqT2DQAA
} 64#{
eJztlztyxDAIQEkmbcIVPKn2HGlS5Q45RWofzcUeiGpbBSHZQp9Z2PGWwma9yG8k
LBiDv35u7yDyx3ph/Wb9ZX2BNxlf+f71I2ktq5ywrukSD/6zbRtf40iQE0JIl3jE
If75bKfq5HVZgiW0oMkEOM8QOhjwMOhgwOEPAybDHmmmwjEvBRUjz1AbHQN6zmw0
+0MS88ZomDhtZ0C1h7I0tEbNpFzBgfEQQznpesNmyMHouPMwomKyUcedDaTiczbE
8/Px6piBeJgARGYeVs9+htm5yUxmMpOZjJ950jta3vf3GVKV02RGvUTLDGpc8QdS
VRzVyp2hXFxHNXdnRK0egNrhB/sEzTyjb6Hi9LE7evMPZtxrlbWOvkFtszIKczZe
JTf6XrQw4X76SNzRZshIw5BTx2TIwTh8NsXFeL6JPN9W/6HQFrv2DQAA
} 64#{
eJztlzF2wyAMQNW+ri1X8OvUc3Tp1Dv0FJ19NA89kKauqhAQg4QtkjhD34tsTIQ/
IFsKyO+fv88g8s3ljcsHly8uD/Ak7TPf/3lJpZVZTpjnVMWDfyzLwnVsITmBKFXx
iE18edVDGXmcJvIEp+AyBNczbE44gmGLD2HwEnsaPCkgl/UGQjNJl4F6zKxAazOK
z5WiGACwSmszShxoBSWeQtXzNFCjnMVgDjqr+AwOMPF+xYRQMVnRz0UBV5uzon3a
8ZdM6vjLMB0ZYQhwIJ7rZ7+GKdyd+ccMAriM38J/BiQ9kmI4fBFBNxqmWYduyWCz
qFzOUGcqw6Adxr5VO8yt/EVplfQYK3fmGOagNVrW+32mF3mbTC+X0EwvJznZA2lX
7O2VhcG8ufb23MJI8XIA1M1n5gk1c0DeUm3lJkeq3mHpu5F4hbiBl77Va66Uldnz
F+VMcddfhenlosrmbRHGjVUnUMeZMXtcxptpkBn5Jhr5tvoDMlDOXvYNAAA=
} 64#{
eJztl0FywjAMRVWm2+IrZLriHN10xR04BescLYseSKtuXVk2YMtCMiUzDDMocYyc
R+xE8o/ztf/9ALYjlR2VbyoHKm/wzu0znf/Z5tLazDvMc67SRj+WZaE6tUTeIcZc
pS010eFTXqqzzTRFz3AKLhPBZjCddxlwGQZcBl2G+nIZGGFkXw0eVAahGYjKQH3N
4ggGOebCEWMGgN5pnzNyHmjOhcm5EhTnJgZL0vWOz+AAU+dYCnComOK0905OwMuY
iyPjrsSLO3Xi1TGKjTAR0M/n5t7vYU7ci3lKRtMNyWj60zL6vGiYK/NLMv+e77cy
a2nUkB7quioYVZ8Fs1K8ovre6Zhr9mLWYVbSaNZ7m8E6LT3GyLEzY+VYTm9bE7C8
XE1NSMWbyyibH6sJmBcfpiaUf5iawGs3RxMKY2rCiTHjVRhLEwCcRMyMieS+HPOX
0NVztpk4wgyMx2dGvolGvq3+ALZph/D2DQAA
} 64#{
eJztlztSxDAMhgVDC7pChopz0FBxB05BnaOl2AOpojXyI4llyY/dBGhQ4s1K/mLH
tvxn8vr+9QjBPrm8cHnj8sHlDh5CfOb6y1Ms0uZwwjzHiz/4z7IsfPURF05wLl78
4UP881w2pex+mlzPaMIu4+AwQ3EMxxl0XcYTpzCEBSNwNBmCvAEwGcjbXB0QTJyL
0pGMX2LtCIZCHihHMDFX0HCuYiglnXb6DA0wvj5jEDMmOSTyhx2k/ZmTE568vV6h
0856KcawEcYBEXb3Vz72I8zK/TO/ypj7vWAs3ZCMnc+CqeyLkrl5n17LnKUtNR0T
TEUPJWPrqmROWi+XXhGUOkGbGYzwYpMDaDI8HCIog4oRU/OTDImkuJ1xRleKId2M
nlXdjGZINWNoq71eMkol4uuD3reM19kYSZVp5OrGtHI1zkhbWyi9XJva4ktPE6gM
/7G27K/7urbAdm9dW/YHzaa50BZY721oS0qNpras6SOyCG2mZqHbdoqNMtDrbMux
40zXhpiRb6KRb6tvHjBX8/YNAAA=
} 64#{
eJzNlz1WwzAMxwWPFXyFPKaeg4WJO3AK5hwtAwfS1FXYslNsWbFUmpeiRHXl/F4s
W/98vX2cn4HtK/op+nv0z+gP8MT9czz+/ZK9tZl3mOfcpC3+WZYltqmHeAei3KQt
dcWfV3mqzh6niSzDKZgMwe0M8hzGTEw5mAzhYUyXc4PnQOaM0JxAHQvqc5ZAMMg1
F4FgAKAP2nyQdSCDdl5ZK0EJrmKwiK4PbAYdTDpeMSFUTAnkvCjgb84lcNSLBzXq
1TGKeRgCtHXYzP0WZuWuYRTNS0bTvGQ0zQtG1bxgVM23jK75lvkHmv8Do2le5qxp
XqzhXvXatjsxnpztuTvW0FOLI7Wxl55d147nGnRdyzvVi1z3qE3zMjvdo/l+P2aw
XnaLGazhhRmtYS7fWPNYHq5DzSe3tIqy+76ax/VtaKB5uLxWbWue38sMzRdmqPmV
setFQ82jIZ96rEOYZtI648nZNBfj+SbyfFv9AGTB+m/2DQAA
} 64#{
eJzNlz9WwzAMxgWPFXyFPCbOwcLEHTgFc46WoQf6Jlbjv7EtK5ZpC8Wp60r5JbWl
z8rL6/vXI4X26fqL62+uf7h+Rw/Bv7rzp6fY27aGD61rHPzhfmzb5kbvseFD1sbB
H97lvp75rbp2vyxWa1iMyli6nEFYw5hxUzYqY3E7psGjwecMqpBkEFs71fdMRiCL
H+EaZoTThSGi3mjjjKCDzmiYqBUjGD9ikETXGzqDCcafrxhjKiYZYOuyBmXOyZjI
V/hTJV8dI7QZxhJ0HTZrn2UEre7cPsFmtiIjaZUxolYZI2q1ZQ602jD/QKtnMJJW
+ZwlrbIYFnkWo2NyOTGVwZl4uYElanyccRMFiDs7pknnbzJoAnweQ6kIpFGMTwgt
cm2S4pxTFKuJmK891YhdynuRjPOoGsNFOrzW3pnapzP7fapuzNSfqTo2Uw8P2zn1
94AJ9X7MoA6pxgziszOj+OSNMKo/SA/XYR3zXdMhuPu2ekZ5lB/qGTnMQz3vN6rC
zPWMriRzPedaq+fLDvVMVtXY3zK8rgsMFMlf4x0kMjPvRDPvVt855Lq+9g0AAA==
} 64#{
eJzNlztWxDAMRQ2HFrSFHCrWQUPFHlgFdZaWYhb0KlrjXxJLVmzNwJkZJ05G9p34
o2c5ef/8eXYpfYf8FvJHyF8hP7inVD6H+tNLzjzN6XTznG/xCD+WZQn3WOLT6bzP
t3jEonB5lY9q0uM0+VHCREPGu78zue9dBs7RkCHckGE4qQxc/QSnMq5+ZjYkg+Rz
bkgmulgaYg6RdCAN7ousFVKMsxgU0bXGmIHKgDGxvmKI8qqgykApWbvpCbl+N3zq
eeOvMp7ir9Si9Be2ySON2WeG+bBhwCYm/AWgEcPGXjdVC8nCrNw5TJ4zIDGkMkhz
T/DZCSpTmihX0e992L54kS+AvuYFo2ueM3eg+QuYInNpKJpvjUGMahglRjXMYboR
Y+nzeOyGObT44pra+C89m9aOZQ2a1rIlJphiiyVG7YO+MB6yfo+ZJoYXJsX7PgNW
qDRVM8re1DDKHrf3x+VdUdsrVwZlc9X2XDb2o717ZeDvSfPIo+xqHrTO7LHm01uZ
19/ZJNOLURvTiVEbo7yLtsxRujojRa4wkCJvmG61nbF8E1m+rX4BEApi6fYNAAA=
} 64#{
eJzNlz+OEzEUxh+IxvKyvsKKinPQUHEHTkGdI7i1UuwV3EbTbEG/V3kVEtIK833P
9mTsTNiVFgmcTDKZ+fnz+2d78unLj/di7RuOjzg+4/iK4428s+sH3P9+W4+xHewt
h0P94gsnDw8P+OaVYm8ppX7xxUv4+DBLXbS3d3fluaZ34VmmyOsZ5f2/wBR5gT3/
iLmShrBlupoml5yLMcVYNJRgmd0wyhMXK+NC8fmSsVNN4kSiQObkQ6hVsjJqtaKp
ttAYBvrMmIkkm7WVKVub7SbOPFqwTmbPwLTelVEqmF/lnNPQxiia0YB0HZUrDC48
rn6dbe6mdkarjm59nxkOE+rlsMs8Kaw2nZlRXRkpLT4Dg3gqDFTfxmoMPLzIl1+A
LOZKi3MoU750QQTJlL34tPLJhOS+1PVgzldlFsnZu3T/BNPg+i7joZNdSlJXlx2m
F0b1WPZ01ELUGYTlkgEiFkJrytjNjDrInHV4HgcGUZYUoZN6PYOKbmSyw1xA32M6
8R1hCybIyCyEqHP0x6OnZPDzWIKwUCfl03LKJ5qT8zSWXxapOqCOMbME/DiWpxA7
B7oMt3JAVvLELBQiZS2zk020rV+4RCGpQsitk0lHsq9CraGL00nHQdtqq9cIhJJM
9phBuUcZfmUKjToZ3vJozVehUQfxpUHwvah68WbRFOcTDeI9xfxE/NDBc6XaMi5m
PzjGsSdGflL+bDQ98GmKswnl7jwmFw0amYVCMji2yBxnxt7nllLkZGGXKYZMouSe
UxPKc04zq0FaSknByYmBDjpnqzArfgv1mAsIobOrlWpFS6FRJ3qaeq54TCXMgynO
nLnSZw50osy14YSzxfUos/ijlHmseSI7bh7jfLfIbRYErnEzw0VCNjpSdhiVDSMV
mRi1rYGrHFa7pSGzToEJNyUlz1Uz7zK2O+I8co/LFwztC7ou3LaMtx14ZX7d4od3
2X5wv1iWvgGsixy3jcpwVSQjbYdZmbrQVkZv6h7Xd6qRoT1I0NPK1MsDw03EFuld
xrZV09GrjG3P1Z7HzvSdfC8+Qa4w9YnBQib9caMONee0x7ky7QllZGqcz4VhMmVj
c2daPcdSpnzp6hfmQsQEcXEd4Jwv7q60pz6s4anNglkuGPilfOIDk6rQWGMarj9D
bpg/tv+R6bPytcxz7UXMS/4TveS/1W9aPkPU9g0AAA==
} 64#{
eJzNlz1y3DgQhXtdTlAou6+gcrTn2GQj32FP4VhHQNqlgFdAOoVgFeyBELmKibHv
NTg0CXA1CjYwR9RQmG9e/7JB/fH1+yfx4xvO33H+ifMvnL/JR19/xuf/fO7n+Xj2
H3l+7m984eL19RXvXGn+I631N764hF9fRqnp+PD01B4d9UkfMk3+B0YAPGCqR/iA
0fbLMv9RBj0ym1qNJcdQa1XpyyNTqVdyCVWVqcPCxPhyKWCaQKkzhxyqpxQXEbYU
H246x1qou61kSlBtNfBaodPaT4aqQlvZuB6kM3pktliLgJEqmpzpFjam9oTUGIPB
mZ2hsYFB8GFRfBlMLfns857YbKltTM7n2HcmWHXGlDq+dsG4E7Iq/RmZWsnY4itX
DKKtvCuDJXjC4NW/AmyqF9xBivmV5HH9+NzGeoktkpliJkiG/Gz9E0xKjCI5bjrt
WIt14V8QQ+VBFcQwMmsKC2QYR8k4i+fuxIjFbC2bQSlGFB+choEJ0KlRLQGCRzG7
TycmyApE8koIOjAVbyOTVWORqKshrx5atCgnJnZfU250KdLtK4YYmhDWtIc2Md7Q
lrItsIZUi9yWmfGOziiGJYYVb+mCqWYMGdbk9gJ3dGAQF1sDq1UgBKZYHRn2Ukgs
uYQVDEyNOuJxUUZXUdgslkadiCplyrD3ZIXHppNOKbHsa8EMMrNOYd6OI+xCB8t2
PGYd7yo9vTyEE8N76fxiYQcGt+DZFjt70tGTzwwiXuvsfyMVZdYJViTULY05IV2z
jr1gXi4SOJG3nht1xEo09KGw3bLRuzDqrC+ZzJK6DHvlNuoE9lUOtiLqEjjx2uQP
PJai3qvezzozahm3aMbkaGz9JDIz4SXznoEQrOWt5ybGcysQWmpOjl8wlbNA0WFs
y4KBeI5LY+AY4DCosFZzlGYtDIxk7UMFhbCFiFg9M4Etk6ETfY6nv9fkO8eRYR19
XHhbc/eCThv6Odd9WmpzI9ZmBk5gFMCj1frOfWA4pmlrEZ8wGUNRtx14Z3zcUwfL
yslo+0Z7Z/q2wc+5j7aaVtt3mDvTb0qYCT0dMNXuO9UVQ9ewZbT9fj4y8Kf1zbau
F0zfVnNutTPononp23O5MxxQ7b6Tn/PD1Cy8z4PFqBPDq41B8BiKcTOmx1pIZ9Q3
1FTKZuxYr9qZxgcsDZiX+5POyWf1By20Zwuo/H0HbqfYO4MnrLpyF5zr1e4MhGSF
LTK1TQyv8cRXoYM7SIf+6Y9318eJefP4BRmvzNtMu4f4FvPweBfznv+J3vO/1b/O
39P79g0AAA==
} 64#{
eJytVzuS1DAQbSgSlYrtK2wRcQ4SIu7AKYjnCE5dE3AFpVNKNuBAHREi3mtZXkv2
4F1AXtuz9pvXrz9qaT59+flefHzD+RHnZ5xfcb6Rd/78gvc/HurZj4v/yeVSbzzw
4enpCXc+Kf4npdQbDz7C5cNItRtvHx/L2bBHPcUU+XeMiIn+B4xH4Axjeo7xwJ5h
7FU8d9KgWz28Wl5HI9etX36VxAFI4jf8semAsYkDMP7nL3QbQ/FrmDEA+053ZIfR
VTlQ9aMu9KtfLsGVeynyKkcYhavFMBZGLZWqw9C6EGM1PACYDLYob8CQbbCl1VYL
s7q6E4xfX4+BwoYxu4OBPqualUDmSzs9YuAhxGTNV/n1sPXdqKdUW9ryNcTH3JY6
ka310+XCohDEGMKqHGJUEtWpW+LnA8wEoinBjDJptygHGCFR8oBIihIXzR1mBlGI
BKSEe4pxXz9CY3iXRMKUJtxm2fHMMMZXOc+44Yg3HXmCC5qzRGBACKK046ExYMAD
YRhyvfUYjCZaJPoBwh4Tc6qiWfbgy3Tv2mEiZFBQRE1YoRo4GHKPSZYXDAb8I08c
9CjjQedpK1WeUQ+nMJ8nzkIE+UAPJxK+7GHxjLqe0XefdzGw/WTyMGXzwMMo54WH
eQdVkJEHmXJb0HWLEV6J9TmFywwcbBFDHhSUDTxBSL/aiqw37XnCVRahVbN3jYEn
3NzbZ81rj9rkYhZ23bjEkHgGdbTlUZaaCxRimIaGWkImT0o+e5h3DUxzb6vqea4N
YFBTB3oYuFpjCOCU817PUqGeNmJqG9/qmVjmIYZrzjfOC4Nfw7y4TR6WNF8l3ji/
FJHvMUrHfH5dc/J61DDyqIXJX80+46vokQfOsKoCW8IiesfDlaDlXVub2WHAhOpB
c1WfPTS2x4ApedNE3ap599tjADKSsHUqgm1HmLI0XmWHZf5XjMmiWUptz/6lHsN2
31brdTGA1Q3Glw1tq4hD3FbZYNaFylejZdkpdzD+oerRv8D4snqC8eVZ22rtkkle
yi4+W4xvRHqeumNo+6Y1hj2m1o+smxF/Xg4wy26GpS5tRmwa6mLdCbBD4kapdjkb
MfVbrFXfcm3KQeq2zOuw1L2d79yqlGeMtUbDNaGN1GPkD3vIlotGcm8QcwJ5KY+e
ES1+nWA8hv8BcyLmpZiX/CZ6yW+r3/c0VV/2DQAA
}

; back

64#{
eJztlzEOAiEQRdHYkKzDFYiV57Cx8g6ewpoj0O5tKCw5BAfYbLayxZmhUpMB+xl4
y4b9gc3r5nJ7HQ3XAzkjV+SO7MyB9wN+f0LjswJPE0JbaOBLSglX2qk8Ta1toUFb
+Dh9H/VTe+9rrzbvuplqXO1chQG3WcggYCgzxyVJcAYmK8DnxLXMApEyE9hZgDMZ
b5WgDGQLAvw/ZUmrQFE/6kf9qB/1o37Uj/pRP+pH/fzlZ6QHGehlRnqikd7qDVAb
l+X2DQAA
}
]

Next, I wrote a little GUI app to test that all the cards display appropriately. It builds a GUI block by reading, decompressing, and appending the data in the card block above, using the built-in "image" word to display each decompressed card. That GUI block is then viewed with the typical "view layout" code. All of the code in the rest of this section will assume that the card data above has been defined in the interpreter:

; I want the cards to be layed out next to each other in the GUI, so
; the layout block starts with the built-in "across" word:

gui: [across ]

; The "count" variable is used to separate each suit onto different
; lines in the GUI:

count: 0 

foreach card cards [
    if count = 13 [
        ; after the 13th card, start a new line in the GUI
        ; and reset the count:
        append gui [return]
        count: 0
    ]
    ; The following code adds the image data to the block that'll be
    ; displayed:
    append gui compose [image load to-binary decompress (card)]
    count: count + 1
]

view layout gui

That provides a fundamental way to compress and reuse card images to create all types of games. Adding the "feel movestyle" code presented earlier in this tutorial allows us to click and drag the cards around the GUI. Here I'll make some changes to the code because I want the cards to move using "snap-to" positioning, as if they're placed on a grid and clicking from one grid position to the next (as opposed to floating freely across the screen with each click-drag):

; The following function enables the pieces to slide around the
; screen.  The coordinates are rounded to multiples of 79 and 104
; pixels to enable snap-to positioning (the horizontal width and 
; vertical height of each card and the surrounding space).  The
; additional 20 pixels accounts for the default border around the
; overall GUI:

movestyle: [
    engage: func [face action event] [
        if action = 'down [
            face/data: event/offset
            remove find face/parent-face/pane face
            append face/parent-face/pane face
        ]
        if find [over away] action [
            unrounded-pos: (face/offset + event/offset - face/data)
            snap-to-x: (round/to first unrounded-pos 79) + 20
            snap-to-y: (round/to second unrounded-pos 104) + 20
            face/offset: to-pair rejoin [snap-to-x "x" snap-to-y] 
        ]
        show face
    ]
] 

; Here's a revised version of the previous GUI block.  The only
; difference is that is uses the snap-to positioning definition
; above ("movestyle"):

gui: [across ]
count: 0 
foreach card cards [
    if count = 13 [
        append gui [return]
        count: 0
    ]
    append gui compose [
        image load to-binary decompress (card) feel movestyle
    ]
    count: count + 1
]
view layout gui

Now you can pick up any card, move it around the screen, and it automatically lines up and snaps over any other card on the screen - very useful! Steps 1 and 2 in the program outline are done. Next, I'll remove the card backside image from the card data, and tile all the images on the screen. That just means changing the initial positions of the cards, and also the number of pixels rounded in the snap-to code:

movestyle: [
    engage: func [face action event] [
        if action = 'down [
            face/data: event/offset
            remove find face/parent-face/pane face
            append face/parent-face/pane face
        ]
        if find [over away] action [
            unrounded-pos: (face/offset + event/offset - face/data)
            snap-to-x: (round/to first unrounded-pos 79) + 20
            snap-to-y: (round/to second unrounded-pos 30) + 20
            face/offset: to-pair rejoin [snap-to-x "x" snap-to-y]
        ]
        show face
    ]
]

gui: [across ]
count: 0 
ypos: 20
foreach card cards [
    if count = 8 [
        ypos: ypos + 30
        coord: to-pair rejoin [20 "x" to-string compose (ypos)]
        append gui compose [at (coord)]
        count: 0
    ]
    append gui compose [
        image load to-binary decompress (card) feel movestyle
    ]
    count: count + 1
]
view layout gui

That code is really starting to look and act like a card game :) Now that we've got a working example of movable cards laid out nicely on screen, I begin to think about how the game will be played (step 4 in the overall program outline). The first thing I realize is that there's absolutely nothing in the "movestyle" code that allows me to determine which card I'm touching at any given moment. Those cards are simply collections of binary graphic data displayed on the screen. The only unique and meaningful information I can get about any given card image is its current position (face/offset). To operate the game, however, I need to know a card's face value, suit, color, etc. A simple solution to that problem can be managed by keeping track of each card's current position, and mapping those locations to individual card face values. To do that, I'll create a block that maps each specific card to its initial coordinates, and then update that block every time a move is made. Every time a card is moved, the given card's new coordinates will overwrite the old coordinates in the block. That way, I can figure out a card's face value simply by reversing the thought process. By looking up any card's specific current position, I can perform conditional evaluations on the related face value, suit, color, etc. of the card at that location ... Sneaky, huh? Doing that additionally allows me to keep track of how the piles of cards are laid out in the playing field - that's also going to play an important role in the game.

The block that maps coordinates to face values can be easily created during the foreach loop that builds the gui layout. In order to keep track of the positions of each card, I need to define some additional variables. Because I used Rebol's automatic GUI layout capabilities to arrange the cards on screen, rather than specifically positioning each image, there are currently no existing variables that store any of those position values. To store the starting coordinates of each card, I create the additional variables "cardnumber" and "xpos", along with a block labled "card-coords" to hold all the values. Now, as each card's image data is layed out in the gui block, it's position is calculated and appended to the card-coords block:

gui: [across ]
card-coords: copy []
coord: 0x0
count: 0 
cardnumber: 1
ypos: 20
xpos: 20
foreach card cards [
    if count = 8 [
        ypos: ypos + 30
        xpos: 20  ; start a new row every 8 cards
        coord: to-pair rejoin [20 "x" to-string compose (ypos)]
        append gui compose [at (coord)]
        count: 0
    ]
    append gui compose [
        image load to-binary decompress (card) feel movestyle
    ]
    coord: to-pair rejoin [to-string compose (xpos)
        "x" to-string compose (ypos)]
    print coord
    ; Add the coordinate of the newly created card to the 
    ; card-coords block:
    append card-coords compose [(cardnumber) (coord)]
    count: count + 1
    cardnumber: cardnumber + 1
    xpos: xpos + 79
]
view layout gui

print "The cards and their positions are: "
probe card-coords halt

Tada! Now every card has a numeric label and each label is tied to a specific starting position.

; These numbers are significant, because all the cards will be
; referred to by them throughout the rest of the game.

1-13  = clubs       ace through king
14-26 = diamonds    ace through king
27-39 = hearts      ace through king
40-52 = spades      ace through king

It's important to note that all changes to the current coordinates, and any other calculations, conditional evaluations, etc. need to occur within the "movestyle" block. Remember, the gui block is simply statically created - it's only run once when the script is first evaluated from beginning to end. It's the movestyle block of code that gets evaluated every time a card is touched. We can include any code that needs to be run, inside the sections that are evaluated when either a down, over, or away action is detected. For example, we can use the following formula to find the index number of the card's initial coordinate in the card-coords block:

new-pos: (index? find card-coords start-coord) / 2
; divide the index number by two because each card has two values:
; position number in the grid, and coordinate.

With that index number, we can look up the name of the card at the same index position in a new "card-names" block using the code "card-names/:new-pos" (that reads: the item in the card-names block at the index number created above). In this example, those values are calculated inside the movestyle block, and a message is printed to demonstrate the mapping technique. Take a close look at the print code to see how each of the variables work:

card-names: [
    "ace of clubs" "2 of clubs" "3 of clubs" "4 of clubs" 
    "5 of clubs" "6 of clubs" "7 of clubs" "8 of clubs" "9 of clubs"
    "10 of clubs" "jack of clubs" "queen of clubs" "king of clubs"
    "ace of diamonds" "2 of diamonds" "3 of diamonds" 
    "4 of diamonds" "5 of diamonds" "6 of diamonds" "7 of diamonds"
    "8 of diamonds" "9 of diamonds" "10 of diamonds" 
    "jack of diamonds" "queen of diamonds" "king of diamonds"
    "ace of hearts" "2 of hearts" "3 of hearts" "4 of hearts" 
    "5 of hearts" "6 of hearts" "7 of hearts" "8 of hearts" 
    "9 of hearts" "10 of hearts" "jack of hearts" "queen of hearts"
    "king of hearts" "ace of spades" "2 of spades" "3 of spades"
    "4 of spades" "5 of spades" "6 of spades" "7 of spades" 
    "8 of spades" "9 of spades" "10 of spades" "jack of spades" 
    "queen of spades" "king of spades"
]

movestyle: [
    engage: func [face action event] [
        if action = 'down [
            start-coord: face/offset
            if (find card-coords start-coord) [
                new-pos: (index? find card-coords start-coord) / 2
                print rejoin [
                    "You touched card number: "
                    (select card-coords new-pos)
                    "^/Position #" 
                    new-pos " in the grid."
                    newline
                    card-names/:new-pos
                    newline                 
                ]               
            ]
            face/data: event/offset
            ; remove find face/parent-face/pane face
            ; append face/parent-face/pane face
        ]
        if find [over away] action [
            unrounded-pos: (face/offset + event/offset - face/data)
            snap-to-x: (round/to first unrounded-pos 79) + 20
            snap-to-y: (round/to second unrounded-pos 30) + 20
            face/offset: to-pair rejoin [snap-to-x "x" snap-to-y]
            print face/offset
]
show face
]
]

gui: [across ]
card-coords: copy []
coord: 0x0
count: 0 
cardnumber: 1
ypos: 20
xpos: 20
foreach card cards [
if count = 8 [
ypos: ypos + 30
xpos: 20
coord: to-pair rejoin [20 "x" to-string compose (ypos)]
append gui compose [at (coord)]
count: 0
]
append gui compose [
image load to-binary decompress (card) feel movestyle
]
coord: to-pair rejoin [to-string compose (xpos) "x" to-string compose (ypos)]
print coord
append card-coords compose [(cardnumber) (coord)]
count: count + 1
cardnumber: cardnumber + 1
xpos: xpos + 79
]
view layout gui

Using those techniques, I can finish up the card tracking code. The following code makes changes to the card-coords block every time a card is moved:

movestyle: [
    engage: func [face action event] [
        if action = 'down [
            start-coord: face/offset
            print find card-coords start-coord
            face/data: event/offset
            remove find face/parent-face/pane face
            append face/parent-face/pane face
        ]
        if find [over away] action [
            unrounded-pos: (face/offset + event/offset - face/data)
            snap-to-x: (round/to first unrounded-pos 79) + 20
            snap-to-y: (round/to second unrounded-pos 30) + 20
            face/offset: to-pair rejoin [snap-to-x "x" snap-to-y]
]
show face
]
]

And with that, we have the necessary code to keep track of every card's position. The program as it currently exists is a useful generic foundation for any card game. Now we need to begin working on the game logic for freecell. Here are the main objectives, along with some pseudo-code ideas to help organize the thought process:

  1. If a black card is placed below any red card at the bottom of any of the 8 "physical" piles of cards, or visa-versa (red-black), check to see if the moved card is 1 card lower in value than the card it touches. If not, don't allow the move. For example, a red 8 can be moved below a black 9, but moving a red 8 below a red 9 (not alternate red-black), or a black king beneath a red 3 (not consecutive), isn't allowed. You can make a disallowed card movement happen programmatically by resetting the face/offset of any disallowed card back to the value it held before being moved (that value must therefore be saved at soon as a card is touched).
  2. Only cards exposed at the bottom of pile, or one of the cards in a descendingly stacked group of alternate red-black cards at the bottom of a pile can be moved. For example, in a group of cards r7, b6, r5, b4, r3 at the bottom of a pile, you can move the red 7 and all the cards underneath it to another pile with an exposed black 8 at the bottom of the pile. You could also grab the black 4 and move it, along with the red 3 together, beneath a pile with a red 5 at the bottom. You could not, however, grab the red 7 from that pile without also moving the rest of the cards (b6, r5, b4, r3 ) beneath it.
  3. The goal of the game is to move all cards from the originally displayed 8 piles to 4 new "goal" piles that are initially empty. Upon completion, each pile must contain only cards of a unique suit (clubs, diamonds, hearts, or spades) and the face values must ascend from ace to king consecutively. Disallow any card movements that don't allow for that arrangement.
  4. There are 4 additional spaces, or "free cells" (the name of the game), that can be used to temporarily hold and move cards around between piles. They are useful in moving cards when there are no positions within the initial piles or in the goal piles that allow a card to be moved according to the previous rules. Only single exposed cards (no covered cards or piles) can be moved to a freecell.

With all that done, I've noticed a little bug in the previous example. When I move a card, it sits on top of, and covers the other cards. That's only desirable if the moved card is the bottom card in it's pile. I'll solve that problem by using the existing block of card coordinates. Every time a card is moved, I'll check to see if it's moved to the lowest position in each pile. If so, I'll make it sit on top of the other cards. If not, it will retain a position underneath the other cards.

The next problem comes when I want to move an entire pile of cards.

Next, I need to randomize the original position of each card. The easiest way that I can think to do that is to create a loop that moves each card around the screen randomly, as if they'd been moved by hand. Just run a feel movestyle on each card.

Next, I'll create a simple background to frame the playing field.

31.7 Case 6 - Vegetable Gardening

Games are fun, and they're great programming practice, but they don't help you get things accomplished in life. Now here's another real-world data processing example to help you think about how to program useful applications in Rebol.

My mother is a retired Microsoft Access developer who loves to garden in her spare time. She's collected a wide scope of knowledge about how certain plants survive better when planted next to each other, and she wanted to create a program to help organize that info. She wanted to create a standalone version that she could use on her home computer and give to friends. She also wanted to publish it to the web as a dynamic database. Additionally, she anticipated creating a version that could be carried into the garden on a pocket pc. I suggested using REBOL, because it could provide a solution for all her needs. She'd been working for several days with her development tools, and I told her I could get the whole thing done that same evening using REBOL. Here's the outline I created:

  1. Create a database structure to hold the vegetable compatibility info and other related information.
  2. Write a command line version of the script that allows users to display all the info for any selected vegetable (this could be run on any operating system that supports the command line version of REBOL, including pocket pc).
  3. Create a CGI version of the above script that works on the web site.
  4. Create a pretty GUI version to be used on the home PC.
  5. Write a separate GUI to manage the administrative adding of data to the database.
  6. Provide a way to update the data files on the web site.

To get things started, I used the listview database example from this tutorial to provide a front end for the vegetable data files. This provided a data structure that was suitable for the project, and it formed an instant solution to creating a GUI front end. Steps 1 and 5 were instantly completed (that database example is so useful - many thanks to Henrik Mikael Kristensen for creating the listview module!).

I created a few initial lines of data to work with. Here's the working database.db file that I created:

["basil" "" "tomato" "basil protects tomatoes." "" ""]
["beans" "onion" "cabbage carrot radish" "" "" ""] 
["cabbage" "celery" "tomato" "" "" ""] 
["carrot" "" "tomato" 
    "Carrots strengthen the roots of tomatoes."
    "Carrots love tomatoes." ""] 
["radish" "cabbage" "beans carrot tomato" "" "" ""] 
["tomato" "cabbage" "basil carrot" "" "" ""]

Each block holds 6 pieces of information about each possible vegetable:

  1. the name of the veggie
  2. a list of other veggies that are compatible with the given veggie (those that do well when planted next to the given veggie).
  3. a list of other veggies that are incompatible
  4. 3 fields for general notes about the given veggie

I decided to add an "upload" button to the listview GUI to satisfy step #6 in my program outline. It made sense to add this functionality here, because the user workflow would generally involve adding/changing data in the database (using the listview), and then updating the online database to match. Here's the upload code I came up with. It includes some error checking, so that the application doesn't crash if there's a problem with the Internet connection:

uurl: ftp://user:pass@website.com/public_html/path/
if error? try [
    ; first, backup former data file:
    write rejoin [uurl "database_backup.db"] read rejoin [
        uurl "database.db"]
    write rejoin [uurl "database.db"] read %database.db
    alert "Update complete."
] [alert "Error - check your Internet connection."]

I added a button to the listview GUI and put the above code in its action block. Here's the complete code I added to the listview:

btn "upload to web" [
    uurl: ftp://user:pass@website.com/public_html/path/
    if error? try [
        ; first, backup former data file:
        write rejoin [uurl "database_backup.db"] read rejoin [
            uurl "database.db"]
        write rejoin [uurl "database.db"] read %database.db
        alert "Update complete."
    ] [alert "Error - check your Internet connection."]
]

Next, I realized that adding and removing new vegetables to and from the database would require some special consideration. It ended up being the biggest part of this coding project. I could use the built-in abilities of the listview module to simply add a new vegetable to the database, but there was a problem with that. Every time a new vegetable is added to the database, it creates a list of compatibilities. Aside from simply adding a new block to the database with fields listing the compatibilities and incompatibilities, that new veggie needs to be added to the compatibility list of every other vegetable with which it's compatible. It also needs to be added to the incompatibility list of every vegetable with which it's not compatible. Editing those blocks manually would take a lot of work and introduce a greater likelihood for user errors, especially as the database grows larger. Instead, I decided to create a little script to do it automatically. Here's the pseudo code thought process for that script:

  1. Create a list of existing vegetables. This can be done by reading the existing database, looping through each block, and picking out the first item in each block (the vegetable name).
  2. Create a small new GUI to enter the new veggie info. It should include an input field for the new veggie name, 2 text-lists showing the possible compatible and incompatible veggies (read from the existing list of veggies in the database), and 3 note fields.
  3. Use a foreach loop to run through the lists of compatible and incompatible veggies. Have the loop automatically add the new vegetable to the other veggies' respective compatibility lists.

I created the GUI code and put the foreach loop inside the action block of a button used to add the new veggie. Here's the code, which I saved as "add_veggie.r":

REBOL [title: "Add Veggie"]

; read the current database:
veggies: copy load %database.db
; get the list of veggies (the 1st item in each block):
veggie-list: copy []
foreach veggie veggies [append veggie-list veggie/1]

; create a GUI with the appropriate fields and text-lists:
view/new center-face add-gui: layout [
    across
    text "new vegetable:" 88x24 right new-veg: field
    return
    text "compatible:" 88x24 right 
    new-compat: text-list data veggie-list
    return
    text "incompatible:" 88x24 right 
    new-incompat: text-list data veggie-list
    return
    text "note 1:" 88x24 right new-note1: field
    return
    text "note 2:" 88x24 right new-note2: field
    return
    text "note 3:" 88x24 right new-note3: field
    return

    ; now add a button to run the foreach loops:

    tabs 273 tab btn "Done" [
        ; First, append the new veggie data block to
        ; the existing database block.  Create the new
        ; block from the text typed into each field,
        ; and from the items picked in each of the 
        ; lists above ("reduce" evaluates the listed
        ; items, rather than including the actual text.
        ; i.e., you want to add the text typed into the
        ; new-veg field, not the actual text 
        ; "new-veg/text").  "append/only" appends the
        ; new block to the database as a block, rather
        ; than as a collection of single items:
        append/only veggies new-block: reduce [
            new-veg/text
            ; "reform" creates a quoted string from the
            ; block of picked items in the text-lists:
            reform new-compat/picked
            reform new-incompat/picked
            new-note1/text
            new-note2/text new-note3/text
        ]
        ; Now loop through the compatibility list of the
        ; new veggie, and add the new veggie to the 
        ; compatibility lists of all those other
        ; compatible veggies.  I put a space in if there
        ; were already other veggies in the list:
        foreach onecompat new-compat/picked [
            foreach veggie veggies [
                if find veggie/1 onecompat [
                    either veggie/2 = "" [spacer: ""] [
                        spacer: " "]
                    append veggie/2 rejoin [spacer 
                    new-veg/text]
                ]
            ]
        ]
        ; Now do the same thing for the incompatibility
        ; list:
        foreach oneincompat new-incompat/picked [
            foreach veggie veggies [
                if find veggie/1 oneincompat [
                    either veggie/3 = "" [spacer: ""] [
                        spacer: " "]
                    append veggie/3 rejoin [spacer 
                    new-veg/text]
                ]
            ]
        ]
        save %database.db veggies
        ; start the veggie data editor again when done:
        launch %veggie_data_editor.r
        unview add-gui
    ]
]
focus new-veg
do-events

Because the add_veggie.r script will always be run from the veggie_data_editor.r program, I added the following code to the action block for the "add veggie" button in the data editor. It launches the above add_veggie program, and closes the listview:

btn "add veggie" [launch %add_veggie.r quit]

When the user closes the add_veggie program, the "launch %veggie_data_editor.r" code at the end of the program relaunches the data editor. This handles flipping back and forth between the two screens. When the data editor is relaunched, all the new data is automatically updated and displayed, so I don't need to manually update any displayed info. After playing with the system, I realized before closing the data editor I'd better save the changes made to the database. So I adjusted the above code as follows:

btn "add veggie" [
    launch %add_veggie.r
    backup-file: to-file rejoin ["backup_" now/date]
    write backup-file read %database.db
    save %database.db theview/data
    quit
]

Next, I used the above code to create a similar "remove_veggie.r" program. Instead of building a GUI for it, I just added some code to the "remove veggie" button in the veggie data editor to save the name of the currently selected vegetable to a file (veggie2remove.r). I also copied the backup routine from the code above to make sure any changes in the listview are saved before going on:

btn "remove veggie" [
    if (to-string request-list "Are you sure?" 
            [yes no]) = "yes" [
        ; get the veggie name from the currently selected
        ; row in the listview:
        first-veg: copy first theview/get-row
        theview/remove-row
        write %veggie2remove.r first-veg
        launch %remove_veggie.r
        backup-file: to-file rejoin ["backup_" now/date]
        write backup-file read %database.db
        save %database.db theview/data
        quit
    ]
]

The remove_veggie.r script just reads the vegetable name from the veggie2remove.r file created above, and runs through some foreach loops to delete that vegetable from the compatibility lists of the other veggies:

REBOL [title: "Remove Veggie"]

veggies: copy load %database.db
remove-veggie: read %veggie2remove.r

; remove the selected veggie from compatible lists (the second
; field in each block).  This is done by replacing any 
; occurrence of the remove-veggie with an empty string ("").
; That effectively erases every occurrence of the veggie:

foreach veggie veggies [
    replace veggie/2 remove-veggie ""
]

; do the same thing to the incompatible lists of all other
; veggies (field 3 in each block):

foreach veggie veggies [
    replace veggie/3 remove-veggie ""
]

save %database.db veggies
; start the veggie data editor again when done:
launch %veggie_data_editor.r

Now the listview data editor and all its helper scripts are complete. Because the listview is generally run from the GUI version of the main program ("veggie_gui.r" - not yet written), I added the following code to the existing listview close routine:

launch "veggie_gui.r"

When I design the main veggie_gui program, I'll add a button to launch the listview. When I close the listview, the above code will relaunch the GUI program to handle flipping back and forth between those two screens. Here's the final listview database code with all the described changes and additions:

REBOL [title: "Veggie Data Editor"]

evt-close: func [face event] [
    either event/type = 'close [
        inform layout [
            across
            btn "Save Changes" [
                ; when the save btn is clicked, a backup data
                ; file is automatically created:
                backup-file: to-file rejoin ["backup_" now/date]
                write backup-file read %database.db
                save %database.db theview/data
                launch "veggie_gui.r"
                quit
            ]
            btn "Lose Changes" [
                launch "veggie_gui.r"
                quit
            ]
            btn "CANCEL" [hide-popup]
        ] none ] [ 
        event
    ]
]
insert-event-func :evt-close

if not exists? %list-view.r [write %list-view.r read
    http://www.hmkdesign.dk/rebol/list-view/list-view.r
]
do %list-view.r

if not exists? %database.db [write %database.db {[][]}]
database: load %database.db

view center-face gui: layout [
    h3 {To enter data, double-click any row, and type directly
        into the listview.  Click column headers to sort:}
    theview: list-view 775x200 with [
        data-columns: [Vegetable Yes No Note1 Note2
            Note3]
        data: copy database
        tri-state-sort: false
        editable?: true
    ]
    across
    btn "add veggie" [
        launch %add_veggie.r
        backup-file: to-file rejoin ["backup_" now/date]
        write backup-file read %database.db
        save %database.db theview/data
        quit
    ]
    btn "remove veggie" [
        if (to-string request-list "Are you sure?" 
                [yes no]) = "yes" [
            first-veg: copy first theview/get-row
            theview/remove-row
            write %veggie2remove.r first-veg
            launch %remove_veggie.r
            backup-file: to-file rejoin ["backup_" now/date]
            write backup-file read %database.db
            save %database.db theview/data
            quit
        ]
    ]
    btn "filter veggies" [
        filter-text: request-text/title trim {
            Filter Text (leave blank to refresh all data):}
        theview/filter-string: filter-text
        theview/update
    ]
    btn "upload to web" [
        uurl: ftp://user:pass@website.com/public_html/path/
        if error? try [
            ; first, backup former data file:
            write rejoin [
                uurl "database_backup.db"] read rejoin [
                uurl "database.db"]
            write rejoin [uurl "database.db"] read %database.db
            alert "Update complete."
        ] [alert "Error - check your Internet connection."]
    ]
]

Next, I created a command line version of the program. The "Looping Through Data" example provided earlier in this tutorial served as a perfect model. I just changed some of the variable labels and loaded the data from the existing database.db file. Here's the code:

REBOL [title: "Veggies"]

veggies: load %database.db

a-line: copy [] loop 65 [append a-line "-"]
a-line: trim to-string a-line

print-all: does [
    foreach veggie veggies [
        print a-line
        print rejoin ["Veggie:   " veggie/1]
        print a-line
        print rejoin ["Matches:  " veggie/3]
        print rejoin ["No-nos:   " veggie/2]
        print rejoin ["Note 1:   " veggie/4]
        print rejoin ["Note 2:   " veggie/5]
        print rejoin ["Note 3:   " veggie/6]
        print newline
    ]
]   
forever [
    prin "^(1B)[J"
    print "Here are the current foods in the database:^/"
    print a-line
    foreach veggie veggies [prin rejoin [veggie/1 "  "]]
    print "" print a-line
    print "Type a vegetable name below.^/"
    print "Type 'all' for a complete database listing."
    print "Press [Enter] to quit.^/"
    answer: ask {What food would you like info about?  }
    print newline
    switch/default answer [
        "all"   [print-all]
        ""      [ask "^/Goodbye!  Press [Enter] to end." quit]
        ][
        found: false
        foreach veggie veggies [
            if find veggie/1 answer [
                print a-line
                print rejoin ["Veggie:   " veggie/1]
                print a-line
                print rejoin ["Matches:  " veggie/3]
                print rejoin ["No-nos:   " veggie/2]
                print rejoin ["Note 1:   " veggie/4]
                print rejoin ["Note 2:   " veggie/5]
                print rejoin ["Note 3:   " veggie/6]
                print newline
                found: true
            ]
        ]
        if found <> true [
            print "That vegetable is not in the database!^/"
        ]
    ]
    ask "Press [ENTER] to continue"
]   
halt

That was easy! Just compare it to the original example - it's virtually identical. Again, that generalized example was presented in this tutorial to provide a model for use in many varied situations. Using it, I didn't even need to write any pseudo code.

Now I extended the above command line example to create a CGI application. To get started, I used the final CGI example provided earlier in this tutorial as a model. To it, I added the code that I'd created for the command line example above. The only real changes I needed to make were some additional html formatting tags, required make the page display properly in a browser (mostly newline "< B R >"s). Again, just an amalgam of several existing examples. No pseudo code required - I just had to think about how to arrange the existing command line code to fit into the general CGI outline. Here's the code:

#! /home/path/public_html/rebol/REBOL -cs
REBOL [title: "Veggies"]
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Veggies"</TITLE></HEAD><BODY>]

veggies: load %database.db

a-line: copy [] loop 65 [append a-line "-"]
a-line: trim to-string a-line

print-all: does [
    foreach veggie veggies [
        print a-line
        print [<BR>]
        print rejoin ["Veggie:   " veggie/1]
        print [<BR>]
        print a-line
        print [<BR>]
        print rejoin ["Matches:  " veggie/3]
        print [<BR>]
        print rejoin ["No-nos:   " veggie/2]
        print [<BR>]
        print rejoin ["Note 1:   " veggie/4]
        print [<BR>]
        print rejoin ["Note 2:   " veggie/5]
        print [<BR>]
        print rejoin ["Note 3:   " veggie/6]
        print [<BR>]
    ]
]

print "Here are the current foods in the database:^/"
print [<BR>]
print a-line
print [<BR><strong>]
foreach veggie veggies [prin rejoin [veggie/1 "  "]]
print ""
print [</strong><BR>]
print a-line
print [<BR>]

selection: decode-cgi system/options/cgi/query-string
if selection/2 <> none [
    switch/default selection/2 [
        "all"   [print-all]
        ][
        found: false
        foreach veggie veggies [
            if find veggie/1 selection/2 [
                print a-line
                print [<BR>]
                print rejoin ["Veggie:   " veggie/1]
                print [<BR>]
                print a-line
                print [<BR>]
                print rejoin ["Matches:  " veggie/3]
                print [<BR>]
                print rejoin ["No-nos:   " veggie/2]
                print [<BR>]
                print rejoin ["Note 1:   " veggie/4]
                print [<BR>]
                print rejoin ["Note 2:   " veggie/5]
                print [<BR>]
                print rejoin ["Note 3:   " veggie/6]
                found: true
            ]
        ]
        if found <> true [
            print [<BR>]
            print "That vegetable is not in the database!"]
            print [<BR>]
    ]
]

print [<FORM ACTION="http://website.com/rebol/veggie.cgi">]
print [<BR><HR><BR>"Enter a veggie you'd like info about:"<BR>]
print ["Veggie: "<INPUT TYPE="TEXT" NAME="username" SIZE="25">]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]
print [</BODY></HTML>]

I didn't like the way the CGI required the user to type in the name of a listed vegetable. Instead, I got rid of the list printout, and added the list to a selectable dropdown box. Here's the final cgi example with the html dropdown box:

#! /home/path/public_html/rebol/REBOL -cs
REBOL [title: "Veggies"]
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Veggies"</TITLE></HEAD><BODY>]

veggies: load %database.db

a-line: copy [] loop 65 [append a-line "-"]
a-line: trim to-string a-line

print-all: does [
    foreach veggie veggies [
        print a-line
        print [<BR>]
        print rejoin ["Veggie:   " veggie/1]
        print [<BR>]
        print a-line
        print [<BR>]
        print rejoin ["Matches:  " veggie/3]
        print [<BR>]
        print rejoin ["No-nos:   " veggie/2]
        print [<BR>]
        print rejoin ["Note 1:   " veggie/4]
        print [<BR>]
        print rejoin ["Note 2:   " veggie/5]
        print [<BR>]
        print rejoin ["Note 3:   " veggie/6]
        print [<BR>]
    ]
]

selection: decode-cgi system/options/cgi/query-string
if selection/2 <> none [
    switch/default selection/2 [
        "all"   [print-all]
        ][
        found: false
        foreach veggie veggies [
            if find veggie/1 selection/2 [
                print a-line
                print [<BR>]
                print rejoin ["Veggie:   " veggie/1]
                print [<BR>]
                print a-line
                print [<BR>]
                print rejoin ["Matches:  " veggie/3]
                print [<BR>]
                print rejoin ["No-nos:   " veggie/2]
                print [<BR>]
                print rejoin ["Note 1:   " veggie/4]
                print [<BR>]
                print rejoin ["Note 2:   " veggie/5]
                print [<BR>]
                print rejoin ["Note 3:   " veggie/6]
                found: true
            ]
        ]
        if found <> true [
            print [<BR>]
            print "That vegetable is not in the database!"]
            print [<BR>]
    ]
]

print [<FORM ACTION="http://website.com/rebol/veggie.cgi">]
print [<BR>"Please select a veggie you'd like info about:"<BR>]
print ["Veggie: "<select NAME="username"><option>"all"]
foreach veggie veggies [prin rejoin ["<option>" veggie/1]]
print [</option>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]
print [</BODY></HTML>]

The final part of the outline that I needed to address was the GUI display version of the program. I needed to create this from scratch, so I came up with an outline and some pseudo code to organize my thoughts:

  1. Display the complete list of vegetables in the database (build the list using a foreach loop similar to the ones used in the command line program, and display that block in a text list widget).
  2. Display the info for any vegetable selected from the text list widget (when an item is selected, collect all the info for the selected vegetable and display it, nicely formatted, in a separate text area widget).
  3. Add a button to run the listview editor created earlier.

First I borrowed some code from the add_veggies.r example to create a list of all the veggies in the database. It uses a foreach loop to cycle through each block in the database, and creates a list of the first item in each block (the name of each vegetable). Then it sorts the list alphabetically. This should be run before the GUI is displayed:

load-data: does [
    veggies: copy load %database.db
    veggie-list: copy []
    foreach veggie veggies [append veggie-list veggie/1]
    veggie-list: sort veggie-list
]

I decided to use a text-list widget to display the block of vegetable names. To display the info for each vegetable, I used a simple text area display. Here's the REBOL layout code to do that:

list-veggies: text-list 200x400 data veggie-list
display: area "" 300x400

To that text-list widget's action block I added some code to display the info about the selected vegetable (it gets evaluated whenever the user selects an item from the list):

; First, build a block of text with all the info about the
; selected vegetable, nicely formatted with newlines and 
; capitalized section headings:

current-info: []
foreach veggie veggies [
    if find veggie/1 value [
        current-info: rejoin [
            "COMPATIBLE:     " veggie/3 newline newline
            "INCOMPATIBLE:   " veggie/2 newline newline
            "NOTE 1:   " veggie/4 newline newline
            "NOTE 2:   " veggie/5 newline newline
            "NOTE 3:   " veggie/6
        ]
    ]
]

; Now display and update that text in the text area widget:

display/text: current-info
show display show list-veggies

Finally, add a button to run the listview data editor:

btn "Edit Tables" [do %veggie_data_editor.r]

That's basically it. Here's the final version:

REBOL [title: "Veggie Matches"]

load-data: does [
    veggies: copy load %database.db
    veggie-list: copy []
    foreach veggie veggies [append veggie-list veggie/1]
    veggie-list: sort veggie-list
]

load-data

view display-gui: layout [
    h2 "Click a veggie name to display matches and other info:"
    across
    list-veggies: text-list 200x400 data veggie-list [
        current-info: []
        foreach veggie veggies [
            if find veggie/1 value [
                current-info: rejoin [
                    "COMPATIBLE:     " veggie/3 newline newline
                    "INCOMPATIBLE:   " veggie/2 newline newline
                    "NOTE 1:   " veggie/4 newline newline
                    "NOTE 2:   " veggie/5 newline newline
                    "NOTE 3:   " veggie/6
                ]
            ]
        ]
        display/text: current-info
        show display show list-veggies
    ]
    display: area "" 300x400 wrap
    return
    btn "Edit Tables" [
        do %veggie_data_editor.r
        ; launch "veggie_data_editor.r"
        ; load-data 
        ; show list-veggies
        ; show display
    ]
]

There are 5 complete local script files that make up the completed veggie program: veggie_data_editor.r, add_veggie.r, remove_veggie.r, veggie_command_line.r, veggie_gui.r. In general, the main desktop applications are started by running the veggie_gui.r script. The veggie_data_editor.r can also be run by itself (remember that it runs the veggie_gui.r program when it closes). In order for the veggie_data_editor to work, the listview.r file needs to be included in the same directory. The created database.db should also be kept in the same directory. I packed all those files into an executable using XpackerX, and sent it to my Mom. The 6th script file, veggie.cgi, got uploaded to the web site. The database.db file was also uploaded manually, but my Mom prefers using the upload button in the veggie_data_editor to update the database on the web site. The veggie2remove.r and database backup files are created automatically when the program is used - they're found in the same folder as the script files.

Whatever language(s) you end up using to complete your programming tasks, you will go through a similar process of thinking through program requirements and organizing code structure. Using existing code from the tutorial, along with the easy coding constructs that REBOL enables, I was able to write a complete solution to all of my Mom's requirements in a single short sitting. Even with popular RAD ("rapid application development") tools, a comparable solution would've taken longer to finish. It would also have been less portable to other operating systems, less readable and maintainable, harder to install, bigger in terms of download size, etc., etc... On top of that, after reading just over 100 pages of text, a new programmer can get to the point of understanding how to accomplish this sort of thing, and how to complete many more varied and useful programming tasks using REBOL. To come to a similar understanding with other development environments would take a manyfold increase in study time and much more experience in understanding various software technologies. For all those reasons, I love using REBOL to actually get things done.

31.8 Case 7 - Scheduling Teachers, Part Two

After several months of using the teacher scheduling application described in the first case study, my business expanded, and the teaching staff grew. With the way things worked in my short initial program, I would have to create a new folder on the web site and compile a unique version of the program for each new teacher. This would require recompiling and uploading a new version, for each teacher, every time I alter the program. I wanted to make a multi-user version of the application to simplify setup and to save maintenance time. I also wanted to add some error checking and a simple password scheme to the existing program. To create a new version of the application, here's my concept in outline/pseudo code form:

  1. Maintain the existing folder and file structure on the web site (http://website.com/teacher/name, schedule.txt, and index.php).
  2. Add a file to the web site containing a list of current teachers and associated passwords. Put it outside the public_html folder, so that people can't download it without a password.
  3. In the application, start by downloading that file from the website (using ftp).
  4. Display a text list of teacher names from the downloaded file.
  5. When a teacher name is selected, request a password from the user and check that it matches the associated password for the given teacher.
  6. Append the teacher name to the http and ftp urls, and run the program as before.
  7. Add some error checking and backup routines every time the data is read or written locally, or on the web server. That way, no data is ever lost.
  8. Compile the program and upload it to the web site. Point all links on the index.php pages to that single file. Now, any time I want to add a new teacher, all I need to do is add the new teacher name and password to the downloadable text file and copy a blank index.php and schedule.txt to a new folder on the web server. If I ever make additional changes to the program, I only need to recompile and upload that single program file.

To start things off, I created a text file called "teacherlist.txt" and stored it outside the public_html folder on the web server. It's formatted like this:

["mark" "markspassword"] ["ryan" "ryanspassword"]
["nick" "nickspassword"] ["peter" "peterspassword"]
["rudi" "rudispassword"] ["tom" "tomspassword"]

The first thing I do in the program is read the data:

teacherlist: load ftp://user:pass@website.com/teacherlist.txt

Next, display a list of the teachers. The first item in each block of teacherlist.txt is the teacher name. A foreach loop reads each of those names into a new block, and that block is displayed using a GUI text-list widget:

teachers: copy []
foreach teacher teacherlist [append teachers first teacher]
view center-face layout [
    text-list data teachers [folder: value unview]  
]

Next, get the password from the user and use a foreach loop to look through the list, checking for a match in teacher names and passwords entered by the user (the first and second elements, respectively, in each block):

pass: request-pass/only
correct: false
foreach teacher teacherlist [
    if ((first teacher) = folder) and (pass = (second teacher)) [
        correct: true
    ]
]
if correct = false [alert "Incorrect password." quit]

I add the following line to the script, which keeps REBOL from terminating the script when the [Esc] key is pressed. That behavior is the default in the REBOL interpreter, and makes it easy for someone to just stop the script and view the teacherlist. (I'm not so concerned about security here, but I don't want passwords to be blatently available):

system/console/break: false

Finally, I come up with an error message to be executed any time an Internet connection isn't available. It allows the user to read any of the recently backed up schedule.txt files so that the program is useful even if an Internet connection isn't available:

error-message: does [
    ans: request {Internet connection is not available.
        Would you like to see one of the recent local backups?}
    either ans = true [
        editor to-file request-file quit
    ][
        quit
    ]
]

I wrap all attempts to connect to the Internet in "error? try" routines, and duplicate the original backup routine from the initial program so that no data is ever lost. Here's the final code:

REBOL [title: "Lesson Scheduler"] 

system/console/break: false
error-message: does [
    ans: request {Internet connection is not available.
        Would you like to see one of the recent local backups?}
    either ans = true [
        editor to-file request-file quit
    ][
        quit
    ]
]

if error? try [
    teacherlist: load ftp://user:pass@website.com/teacherlist.txt
][
    error-message
]
teachers: copy []
foreach teacher teacherlist [append teachers first teacher]
view center-face layout [
    text-list data teachers [folder: value unview]  
]

pass: request-pass/only
correct: false
foreach teacher teacherlist [
    if ((first teacher) = folder) and (pass = (second teacher)) [
        correct: true
    ]
]
if correct = false [alert "Incorrect password." quit]

url: rejoin [http://website.com/teacher/ folder]
ftp-url: rejoin [
    ftp://user:pass@website.com/public_html/teacher/ folder
]

if error? try [
    write %schedule.txt read rejoin [url "/schedule.txt"]
][
    error-message
]

; backup (before changes are made):
cur-time: to-string replace/all to-string now/time ":" "-"
; local:
write to-file rejoin [
    folder "-schedule_" now/date "_" cur-time ".txt"
] read %schedule.txt
; online:
if error? try [
    write rejoin [
        ftp-url "/" now/date "_" cur-time
    ] read %schedule.txt
][
    error-message
]

editor %schedule.txt

; backup again (after changes are made):
cur-time: to-string replace/all to-string now/time ":" "-"
write to-file rejoin [
    folder "-schedule_" now/date "_" cur-time ".txt"
] read %schedule.txt
if error? try [
    write rejoin [
        ftp-url "/" now/date "_" cur-time
    ] read %schedule.txt
][
    alert "Internet connection not available while backing up."
]

; save to web site:
if error? try [
    write rejoin [ftp-url "/schedule.txt"] read %schedule.txt
][
    alert {Internet connection not available while updating web
    site.  Your schedule has NOT been saved online.}
    quit
]   
browse url

With the new application complete, I wanted to create an additional cgi application for the web site to collectively display all available times in each of the teachers' schedules. This would help with scheduling because both students and management could instantly see a bird's eye view of all open appointment times, on a single web page. In order for that display to be viewable by the general public, I want the cgi app to strip out all personal data contained in the schedules. To create the cgi, I need to search each line of schedule text for "----". If a line contains the characters "----", that time is available. Here's a pseudo code outline that I thought through as I organized the process:

  1. Make a list of all the teacher pages. Store the links in a block.
  2. Use a foreach loop to cycle through each page in the list. Read in the data on each page in line format, using another foreach loop.
  3. For each line, use a find function to check whether the line contains the name of a day of the week, or the characters "----". If so, print the line, adding some additional formatting to separate days as headers. Also print each page link as a header separating each teacher's schedule in the printout.

First, I created the block of links:

page-list: [
    http://website.com/teacher/ryan
    http://website.com/teacher/mark
    http://website.com/teacher/nick
    http://website.com/teacher/peter
    http://website.com/teacher/tom
    http://website.com/teacher/rudi
]

For step 2, I created the foreach loop to read each page:

foreach page page-list [
    data: read/lines page
]

Inside that loop, I added the code to print out the teacher name and day headers, and the available times:

foreach page page-list [
    print newline
    print to-string page 
    print ""
    data: read/lines page
    week: ["MONDAY" "TUESDAY" "WEDNESDAY" "THURSDAY" "FRIDAY"
        "SATURDAY" "SUNDAY"]
    foreach line data [
        foreach day week [
            if find line day [print "" print line print ""]
        ]
        if find line "----" [print line]
    ]
]

Now I've got a little command line application that does what I need:

REBOL []
page-list: [
    http://website.com/teacher/ryan 
    http://website.com/teacher/mark 
    http://website.com/teacher/nick 
    http://website.com/teacher/peter 
    http://website.com/teacher/tom 
    http://website.com/teacher/rudi
]
foreach page page-list [
    print newline
    print to-string page 
    print ""
    data: read/lines page
    week: ["MONDAY" "TUESDAY" "WEDNESDAY" "THURSDAY" "FRIDAY"
        "SATURDAY" "SUNDAY"]
    foreach line data [
        foreach day week [
            if find line day [print "" print line print ""]
        ]
        if find line "----" [print line]
    ]
]

Next, to the basic CGI framework provided earlier in this tutorial, I simply added the code above. As with the previous veggie case study cgi, the only real changes I needed to make were some added "< B R >"s to make the text display properly in the browser:

#!/home/path/public_html/rebol/REBOL -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Available Appointment Times"</TITLE></HEAD><BODY>]
page-list: [
    http://website.com/teacher/ryan 
    http://website.com/teacher/mark 
    http://website.com/teacher/nick 
    http://website.com/teacher/peter 
    http://website.com/teacher/tom 
    http://website.com/teacher/rudi
]
foreach page page-list [
    print [<BR><BR>]
    print to-string page 
    print [<BR>]
    data: read/lines page
    week: ["MONDAY" "TUESDAY" "WEDNESDAY" "THURSDAY" "FRIDAY"
        "SATURDAY" "SUNDAY"]
    foreach line data [
        foreach day week [
            if find line day [print [<BR>] print line print [<BR><BR>]]
        ]
        if find line "----" [print line print [<BR>]]
    ]
] 
print [</BODY></HTML>]

As more teachers were added to the scheduling system, it became apparent that a CGI version of the editor would be helpful (for use on mobile phones, at work, and in other environments where installing an executable was problematic). Basically, I needed to create a password protected online text editor, which could be useful in many other situations. I also wanted it to make backups of my previous data files, just like the desktop version. Here's the pseudo code outline I came up with:

  1. If the page has no data submitted, print text fields to get user name and password, and resubmit to page, end there.
  2. Otherwise, read teacherlist, check if name and password are correct. If no, print error message and end. If yes, read schedule.txt for appropriate teacher, display textbox containing current schedule, and button to submit changes. End there. Submit button should run another cgi that backs up old data, and saves new data.

In working with this example, I discovered that data from the textarea could get too big if submitted using the default "GET" method on the html form. To work with POST data, the "read-cgi" function should be used, as in the example below:

#!/home/path/public_html/rebol/REBOL -cs
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Edit Schedule"</TITLE></HEAD><BODY>]

; selection: decode-cgi system/options/cgi/query-string
; We can't use the above normal line to decode, because
; we're using the post method to submit data (because data
; from the textarea may get too big for the get method). 
; Use the following function to process data from a post 
; method instead:

read-cgi: func [/local data buffer][
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    data
]

selection: decode-cgi read-cgi

; if schedule.txt has been edited and submitted:

if ((selection/2 = "save") or (selection/2 = "save")) [ 
 ; save newly edited schedule:
 write to-file rejoin ["./" selection/6 "/schedule.txt"] selection/4
 print ["Schedule Saved."]
 print rejoin [
    {<META HTTP-EQUIV="REFRESH" CONTENT="0;
        URL=http://website.com/folder/}
    selection/6 {">}
 ]
 quit
]

; if user is just opening page (i.e., no data has been submitted 
; yet), request user/pass:

if ((selection/2 = none) or (selection/4 = none)) [
 print [<strong>"W A R N I N G  -  "]
 print ["Private Server, Login Required:"</strong><BR><BR>]
 print [<FORM ACTION="./edit.cgi">]
 print [" Username: " <input type=text size="50" name="name"><BR><BR>]
 print [" Password: " <input type=text size="50" name="pass"><BR><BR>]
 print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
 print [</FORM>]
 quit
]

; check user/pass against those in teacherlist.txt, 
; end if incorrect:

 teacherlist: load %teacherlist.txt
 folder: selection/2 
 password: selection/4
 response: false
 foreach teacher teacherlist [
  if ((first teacher) = folder) and (password = (second teacher)) [
   response: true
  ]
 ]
 if response = false [print "Incorrect Username/Password." quit]

; if user/pass is ok, go on:

; backup (before changes are made):
cur-time: to-string replace/all to-string now/time ":" "-"
schedule_text: read to-file rejoin ["./" folder "/schedule.txt"]
write to-file rejoin [
   "./" folder "/" now/date "_" cur-time ".txt"] schedule_text

print [<strong>"Be sure to SUBMIT when done:"</strong><BR><BR>]
print [<FORM method="post" ACTION="./edit.cgi">]
print [<INPUT TYPE=hidden NAME=submit_confirm VALUE="save">]
print [<textarea cols="100" rows="15" name="contents">]
print [schedule_text]
print [</textarea><BR><BR>]
print rejoin [{<INPUT TYPE=hidden NAME=folder VALUE="} folder {">}]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]
print [</BODY></HTML>]

Now there's a way for all the teachers to edit their schedules. I can add a new teacher to the system in about 5 seconds (just create a new directory on the server and copy blank schedule.txt and index.php files). Anyone involved in scheduling can make changes, regardless of location, and everyone stays synchronized. In addition, anyone can get an instant bird's eye view of all available appointment times - this helps tremendously when scheduling new students.

31.9 Case 8 - An Additional Teacher Automation Project

Now that the group scheduling system is complete, I want to automate our daily checkout routine. Every day, our teachers are paid directly by their students. In turn, they pay us a rental/referal fee for room and resource use. That's our primary source of income. At the end of the day, teachers add up all the students they've seen, and pay a given fee for each completed half hour session. Some students prepay their teachers, and the teachers in turn prepay us so that they don't have to manage rental fees for prepaid appointments in the future.

It takes a lot of time to manually figure daily fees, and the process is error prone when calculated by hand. I want to automate the payment calculations based on the existing online schedule information, and I want to create an integrated record keeping system to more easily track prepayments. Teachers need to keep track of missed/resheduled appointment payments, so that students are given proper credit for rolled over appointment times. Also, in addition to daily local lessons, some of our instructors teach online lessons, for which _we're_ paid directly by students. For those lessons, we deduct room rent from the total paid to us by the teachers. We need a solution to easily manage and track all those daily calculations for all the teachers. The objective is to keep a running total of how much money is due by each teacher every night, and how much money is owed to the teachers by students. To create a software outline, I thought about what I do manually every day to calculate the checkout fees for a single person. This thought process will serve as an outline to design the automated record keeping system:

  1. Each day at checkout time, the total number of lessons for a teacher is added up.
  2. The teacher owes us a given amount for lessons that occured in the local studio that day.
  3. We owe the teacher a given deduction for each lesson they performed online that day.
  4. Any lessons which had previously been prepaid by the teacher are deducted from the total owed us.
  5. The teacher prepays us for any future lessons which were prepaid by students that day, and records are updated to track the current prepaid amounts.
  6. Occasionally, other deductions are made from the amount owed us (sometimes the teacher provides a complimentary lesson for various reasons, or we provide complimentary time to the teacher/student, etc.). Those amounts are deducted from the total owed us.

Based on the guidelines above, here's how I organized my thoughts about what the automated multiuser system should do:

  1. The multiuser requirements of the application are similiar to those of the scheduling app from the previous section. I can use the code from the scheduling app to provide a current teacher list, simple password protection, loading/saving/backup of required data files for the selected teacher, etc.
  2. In order to perform daily calculations for a single instructor, I want to provide a dynamically created list of daily students and I want to retrieve current prepay records for the given teacher. That data will be stored on the web site, any changes will be backed up locally and on the web site. I'll need to come up with a data structure to store the prepay records. All other information (random deductions, complimentary lessons, etc.) will be provided by the user on a daily basis. The regular daily student list and prepay records can be downloaded and displayed in text lists. The other random deductions and additions can be entered manually in text input fields, and displayed in text lists.
  3. By default, each teacher owes a given amount for each of the students selected from the daily list (number_of_students X half_hour_rate). Add to that any fees for additional students not in the daily list (rescheduled lessons, occassional additional appointments, etc.)
  4. For each online lesson, subtract 1 student from the total number of students taught that day, and deduct the appropriate amount from the grand total due.
  5. Subtract any previous prepayments from the grand total due. Whenever that happens, make an adjustment to the teacher's record of prepayments.

To satisfy step 1, I'll use the scheduling app from the previous section of this case study. As it stands, that code is capable of selecting a specified teacher directory on the website and downloading any required data files (current daily students, prepay records, individual teacher fee rates, etc.).

The main work of creating the application is in step 2. The required calculations are in steps 3-5. Here's a more structured outline, with pseudo code, to guide the writing of the program code:

  1. Read a current list of daily students from the selected teacher's schedule.txt file on the web server. Store that info in a block and display it in a GUI text-list widget. Store a list of local students selected from the above widget in a separate block.
  2. Display today's students again in a second text-list widget, so that the user can select those who took lessons online. Store that selected data in another block.
  3. Provide a text input field to allow the addition of any students not in the daily list. Display a text-list widget to contain students entered into the text field. Update the text-list display any time a student is added. In order to remove incorrect entries from this list, the action block of the text-list should contain code to delete any students selected by the user.
  4. Provide another text field and text-list for the entry of deductions, with the same layout and remove code.
  5. Provide a button to manage prepayment entries and calculations. To handle that whole process, create a separate script - to be outlined below.
  6. Provide a "Calculate Total Fees" button. The action block of this button should add and subtract the total number of items in all of the text-lists, according to the rules defined in steps 3-5 of the overall program outlined above. Provide an html summary, which the teacher can print out and submit every day.

Here's the code I came up with to do all that:

; The "url" variable below comes from the multiuser framework
; borrowed from the scheduler app:
students: read/lines rejoin [url "/schedule.txt"]
; Initialize some other variables:
other-additions: [] other-deductions: [] prepays: [] 
pay-for: copy [] online: copy []

view center-face layout [
    h2 "Local Students:"
    ; "face/picked" refers to the currently selected items in
    ; the text-list (use [Ctrl] + mouse click to select multiple
    ; items, and assign that to the variable "pay-for":
    text-list data copy students [pay-for: copy face/picked]    
    h2 "Other Additions:" 
    field [
        ; add the entered info to the text-list, and update
        ; the display:
        append other-additions copy face/text
        show other-additions-list
    ]
    other-additions-list: text-list 200x100 data other-additions [
        ; remove any entry when selected by the user, and update
        ; the display
        remove-each item other-additions [item = value]
        show other-additions-list
    ]
    at 250x20
    h2 "Online Students:"
    text-list data copy students [online: face/picked]
    h2 "Other Deductions:"
    field [
        append other-deductions copy face/text
        show other-deductions-list
    ]
    other-deductions-list: text-list 200x100 data other-deductions [
        remove-each item other-deductions [item = value]
        show other-deductions-list
    ]
    at 480x20
    h2 "Prepaid Lessons:"
    prepay-list: text-list data prepays [
        remove-each item prepays [item = value]
        show prepay-list
    ]
    ; I still need to create the prepay.r program:
    btn 200x30 "Calculate Prepaid Lessons" [
        save %prepay.txt load rejoin [url "/prepay.txt"]
        do %prepay.r
    ]
    at 480x320
    btn 200x100 font-size 17 "Calculate Total Fees" [
        total-students: (
            (length? pay-for) - (length? online) + 
            (length? other-additions) - (length? other-deductions) -
            (length? prepays)               
        )
        ; I want to create an html output for this section:
        alert rejoin ["Total: " to-string total-students]
    ]
]

Now all that's left to create is a separate program to manage prepayment info. Here's an outline describing my intentions:

  1. Create and upload a "prepay.txt" data file to store prepayment information for each teacher. It should contain a separate block for each student who prepays, with fields for the student name, a nested block for the amounts and dates of each prepayment, and a nested block for dates of each lesson attended and the amount deducted from the prepayment for each lesson.
  2. Create a GUI with a text-list displaying each student who has prepayed. Loop through the prepay.txt data to get the student names (the first item in each block). Whenever a name is selected by the user, display the student name, prepay dates and amounts, and lesson dates in separate text lists. Display the total prepay balance for the selected student in a text field.
  3. There should be an "Add" button and some text fields for entering new prepayments. There should be fields for student name, amount, and date of prepay. If an existing student is selected from the list, those fields should be populated automatically with today's date, and with the name of the existing student. The action block of the add button should append the information to appropriate blocks in the prepay.txt file.
  4. There should be an "Apply Today" button to select prepayment(s) to be applied to today's balance. Store the names of the selected students in a block, save that block to be read and used in the main application, and add the date information to the appropriate blocks in the prepay.txt file.
  5. There should be an "Done" button on the list-view GUI to allow the information to be changed and saved. Whenever a student is selected from the list, their prepayment records should be displayed in an editable list-view (import the listview module and use the database example from earlier in this tutorial as a model). There should be fields for prepay amounts and dates, and lesson dates and amounts.
  6. When the main prepay application is closed, the prepay.txt file should be backed up and saved to the web site.

For step 1, here's an example of the block structure I came up with to store data in the prepay.txt file:

[
    ; name:
    "John Smith"

    ; prepayment amounts and dates:
    [ [$100 4-April-2006] [$100 5-May-06] ]

    ; dates of lessons:
    [
        [$20 4-April-06] [$20 11-April-06] [$20 18-April-06]
        [$20 25-April-06] [$20 5-May-06]
    ]
]

[
    "Paul Brown" 

    [ [$100 4-April-2006] ]

    [
        [$20 4-April-06] [$20 25-April-06]
    ]
]

[
    "Bill Thompson"
[ [$200 22-March-2006] ]

[
    [$20 22-March-06] [$20 29-March-06] [$20 5-April-06]
    [$20 12-April-06] [$20 19-April-06] [$20 26-April-06]
    [$20 3-May-06]
]
]


[
; name:
"John Smith"

; prepayment amounts and dates:
[ "$100 4-April-2006" "$100 5-May-06" ]

; dates of lessons:
[
    "$20 4-April-06" "$20 11-April-06" "$20 18-April-06"
    "$20 25-April-06" "$20 5-May-06"
]
]

[
"Paul Brown" 

[ "$100 4-April-2006" ]

[
    "$20 4-April-06" "$20 25-April-06"
]
]

[
"Bill Thompson"
[ "$200 22-March-2006" ]

[
    "$20 22-March-06" "$20 29-March-06" "$20 5-April-06"
    "$20 12-April-06" "$20 19-April-06" "$20 26-April-06"
    "$20 3-May-06"
]
]

Here's the code I created to fulfill my outline requirements:

REBOL [title: "Prepayment Calculator"]

prepays: load rejoin [url "/prepay.txt"]
names: copy []
prepay-history: []
lesson-history: []
display-todays-bal: does [
    ; calculate and display the current balace for the
    ; selected student: 
    todays-balance: $0
    foreach payment prepay-history [
        todays-balance: todays-balance + (
            first (to-block payment)
        )
    ]
    foreach lesson-event lesson-history [
        todays-balance: todays-balance - (
            first (to-block lesson-event)
        )
    ]
    ; update the display of today's balance for the
    ; selected student :
    today-bal/text: to-string todays-balance
    show today-bal
]
foreach block prepays [append names first block]
view center-face gui: layout [
    across
    text bold "New Prepayment:"
    text right "Name:" new-name: field
    text right "Date:" new-date: field 125 to-string now/date
    text right "Amount:" new-amount: field 75 "$"
    btn "Add" [
        create-new-block: true
        foreach block prepays [
            if (first block) = new-name/text [
                create-new-block: false
                append (second block) to-string rejoin [
                    new-amount/text " " new-date/text
                ]
            ]
        ]
        if create-new-block = true [
            new-prepay: copy []
            append new-prepay to-string new-name/text
            append new-prepay to-string rejoin [
                new-amount/text " " new-date/text
            ]
            append prepays new-prepay
            names: copy []
            foreach block prepays [append names first block]
        ]
        display-todays-bal
        show existing show pre-his show les-his show today-bal
    ]
    return 
    text bold underline "Edit Data Manually" [
        view/new center-face layout [
            new-prepays: area 500x300 mold prepays
            btn "Save Changes" [
                prepays: copy new-prepays/text 
                unview
            ]
        ]
        names: copy []
        foreach block prepays [append names first block]
        show gui
        show existing show pre-his show les-his show today-bal
    ] 
    return
    text "Existing Prepayments:"  pad 75
    text "Prepayment History:"  pad 85
    text "Lesson History:" pad 100
    text "Balance:"
    return
    existing: text-list data names [
        ; When a name is selected from this text list, update
        ; the other fields on the screen:
        new-name/text: value
        show new-name
        foreach block prepays [
            if (first block) = value [
                ; update the other text lists to show the
                ; selected student's prepay and lesson history:
                prepay-history: pre-his/data: second block
                show pre-his
                lesson-history: les-his/data: third block
                show les-his
            ]
        ]
        display-todays-bal
        ; get the list of selected students
        prepaid-today: copy face/picked
    ]
    pre-his: text-list data prepay-history
    les-his: text-list data lesson-history
    today-bal: field 85
    return
    btn "Apply Selected Prepayments Today" [
        save %prepaid.txt prepaid-today

        unview
    ]
]

In the original scheduling outline, I replace all references in the code to "schedule.txt" with "prepay.txt":

REBOL [title: "Payment Calculator"]
error-message: does [
    ans: request {Internet connection is not available.
        Would you like to see one of the recent local backups?}
    either ans = true [
        editor to-file request-file quit
    ][
        quit
    ]
]

if error? try [
    teacherlist: load ftp://user:pass@website.com/teacherlist.txt
][
    error-message
]
teachers: copy []
foreach teacher teacherlist [append teachers first teacher]
view center-face layout [
    text-list data teachers [folder: value unview]  
]

pass: request-pass/only
correct: false
foreach teacher teacherlist [
    if ((first teacher) = folder) and (pass = (second teacher)) [
        correct: true
    ]
]
if correct = false [alert "Incorrect password." quit]

url: rejoin [http://website.com/teacher/ folder]
ftp-url: rejoin [
    ftp://user:pass@website.com/public_html/teacher/ folder
]

if error? try [
    write %prepay.txt read rejoin [url "/prepay.txt"]
][
    error-message
]

; backup (before changes are made):
cur-time: to-string replace/all to-string now/time ":" "-"
; local:
write to-file rejoin [
    folder "-prepay_" now/date "_" cur-time ".txt"
] read %prepay.txt
; online:
if error? try [
    write rejoin [
        ftp-url "/" now/date "_" cur-time
    ] read %prepay.txt
][
    error-message
]

editor %prepay.txt

; backup again (after changes are made):
cur-time: to-string replace/all to-string now/time ":" "-"
write to-file rejoin [
    folder "-prepay_" now/date "_" cur-time ".txt"
] read %prepay.txt
if error? try [
    write rejoin [
        ftp-url "/" now/date "_" cur-time
    ] read %prepay.txt
][
    alert "Internet connection not available while backing up."
]

; save to web site:
if error? try [
    write rejoin [ftp-url "/prepay.txt"] read %prepay.txt
][
    alert {Internet connection not available while updating web
    site.  Your schedule has NOT been saved online.}
    quit
]   
browse url

I also need to replace the line "editor %prepay.txt" with new code that does the work of calculating daily fees and tracking prepayments.

Now that the program is complete, please notice how the outline developed. It took several steps. First, I thought through my daily manual calculations. Then I thought about how that could be encapsulated into a program, and I created a basic ouline about what I wanted the program to do. When it came to writing pseudo code outlines to create the actual program, the whole process was made easier by having organized outlines of everything I needed to accomplish. To write the program, I first defined some required data (provided by the multiuser scheduler app), then conceived a user interface, and then performed calculations based on existing data and user input. Following that type of outline structure (define required data, define a UI, perform calculations) tends to be an organized and successful approach in many cases.

It should be noted that I'm not concerned about data security in this app. It is important that the teachers are able to access this info conveniently from any location. It's also important that local backups are made. The automatic backing up of files provides a historical audit trail of transactions and changes to the records, which is an important concern since this program manages income. It's not a problem for these records to become publicly accessible, so I'm using ftp and a public web site to store and retrieve the data. Writing secure applications, however, is an important requirement in most situations involving financial transactions. You should be aware that data security is a primary concern if you intend to do any programming related to typical business transactions, but that topic is beyond the scope of this tutorial. This case study was provided as an additional example of how coding thought can be organized to take you from conceptual phases to a final product. This particular code should not be emulated, however, for projects requiring secure data transactions.

31.10 Case 9 - An Online Member Page CGI Program

One of my friends wanted to create an online member database for a local club. He wanted members to be able to sign up and add their contact information, upload photos, and add info about themselves. He was tired of manually making changes to the members' pages, and wanted users to be able to add, edit, and delete their own information. He wanted basic password enabled access so that users could only edit their own information, and he wanted a back end utility that allowed him to make changes as administrator, and which automatically saved each successive change to the database, so that no data could ever be lost. He also wanted users automatically emailed their password, in case they forgot.

Here was my basic thought process and plan of attack:

  1. This will be an online system (a web site), so the user interface will be a set of html pages that display each user's information, as well as a set of html forms for users to enter information. We decided to have the page display the following fields: Name, Address, Phone, Email, Age, Language, Height, Date the user was added, and an uploaded photo.
  2. The data will be stored in some sort of online database. Since this is a small group with only a few users, I decided to create a simple flat file database - just a text file filled with blocks of REBOL data, one block per user, stored on the web server.
  3. The page that pulls the info from the database and displays it in the above html will basically be a REBOL CGI application that runs a "foreach" loop to print each of the entries in the above html format. The pages where the users enter their information will be forms that submit the information to a REBOL CGI that appends it to the database text file. The pages where the users edit their information will be forms that display the information currently in the selected entry, without the password. When the user submits the new password and updated info, the CGI checks that the submitted password matches the existing password for that entry, and then replaces the old block with the new one, in the database text file. The code for emailing the user a forgotten password and for automatically backing up data will also be put here.
  4. An image upload/update page also needs to created. This will be an html form that accepts a local image file on the user's computer, submits that file to the CGI, which in turn writes that binary data to a directory on the web server, creates an html image link to it, and adds that link to the appropriate user entry in the database text file.
  5. The back end will simply be the password protected text editor explored in case study #6, with links to all the backup text files, for easy recovery (copy/paste) of lost data.

Here was the basic html layout I came up with for step 1. Each entry in the database will be displayed using this template:

<HR><BR> Date/Time: 23-Mar-2008/13:11:42-7:00

<A HREF="./index.cgi?function=">edit | </A>
<A HREF="./index.cgi?function=">delete</A>

<TABLE background="" border=0 cellPadding=2 cellSpacing=2 
    width="100%"><TR>

<TD width = "600">
<BR>
<FONT FACE="Courier New, Courier, monospace">Name:          </FONT>
<STRONG>The User Name Goes Here</STRONG>
<BR>
<FONT FACE="Courier New, Courier, monospace">Address:       </FONT>
<STRONG>The Address Goes Here</STRONG>
<BR>
<FONT FACE="Courier New, Courier, monospace">Phone:         </FONT>
<STRONG>The Phone</STRONG>
<BR>
<FONT FACE="Courier New, Courier, monospace">Email:         </FONT>
<STRONG>The Email</STRONG>
<BR>
<FONT FACE="Courier New, Courier, monospace">Age:           </FONT>
<STRONG>The Age</STRONG>
<BR>
<FONT FACE="Courier New, Courier, monospace">Language:      </FONT>
<STRONG>The Language</STRONG>
<BR>
<FONT FACE="Courier New, Courier, monospace">Height:        </FONT>
<STRONG>The Height</STRONG>
<BR><BR>
</TD>

<TD width="170" valign="center">
<A HREF="./default.jpg" target=_blank><IMG align=baseline alt=""
    border=0 hspace=0 src="./default.jpg" width="160" height="120">
</A>
</TD>

</TR></TABLE>

Some Additional Notes Go Here...

<BR><BR>

The database design for step 2 was even simpler to create. Here's an example of what each block looks like. Notice that each entry in the database is just a text string separated by spaces, for each field of info we want displayed on the member page. In the block, I added a link to a default image, in case the user didn't upload their own photo. This file was saved as %bb.db:

["Username" "19-Feb-2008/4:55:59-8:00" "1 Address St."
    "123-456-7890" "name@website.com" "40" 
    {REBOL, C, C++, Python, PHP, Basic, AutoIt, others...}
    "6'" {I'm a nobody - just a test account.} "password" 
    [
        {<a href = "./default.jpg" target=_blank>
        <IMG align=baseline alt="" border=0 hspace=0
        src="./default.jpg" width="160" height="120"></a>}
    ]
] 

["Tester McUser" "22-Feb-2008/13:14:44-8:00" "1 Way Lane"
    "234-567-8910" "tester@website.com" "35" "Rebol"
    {5' 11"} "I'm just another test account." "password"
    [
        {<a href = "./files/photo.jpg" target=_blank>
        <IMG align=baseline alt="" border=0 hspace=0
        src="./files/photo.jpg" width="160" height="120"></a>}
    ]
]

At this point I could begin the work of step 3, creating a CGI program that prints the html page in step 1, with the above data. Here's a simple CGI script that simply prints the html design together with the entries from the database inserted:

#! /home/path/public_html/rebol/REBOL -cs
REBOL []
print "content-type: text/html^/"
print read %header.html

bbs: load %bb.db  ; load the database info

print [<center><table border=1 cellpadding=10 width=90%><tr><td>]
print {<TABLE background="" border=0 cellPadding=0 cellSpacing=0
    height="100%" width="100%"><tr><td width = "600">}
print [<hr>]
reverse bbs
foreach bb bbs [
    print [<BR>]
    print rejoin ["Date/Time: " bb/2]
    print "                   "
    print rejoin [{<a href="./index.cgi?function=">edit | </a>}]
    print rejoin [{<a href="./index.cgi?function=">delete</a>}]
    print "         "
    print {<TABLE background="" border=0 cellPadding=2 
        cellSpacing=2 height="100%" width="100%"><tr>
        <td width = "600"><BR>}
    print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
        "Name:          </FONT><strong>" bb/1 "</strong>"]
    print [<BR>]
    print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
         "Address:       </FONT><strong>" bb/3 "</strong>"]
    print [<BR>]
    print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
        "Phone:         </FONT><strong>" bb/4 "</strong>"]
    print [<BR>]
    print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
        "Email:         </FONT><strong>" bb/5 "</strong>"]
    print [<BR>]
    print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
        "Age:           </FONT><strong>" bb/6 "</strong>"]
    print [<BR>]
    print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
         "Language:      </FONT><strong>" bb/7 "</strong>"]
    print [<BR>]
    print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
         "Height:        </FONT><strong>" bb/8 "</strong>"]
    print [<BR><BR>]
    print </td>
    print {<td width = "170" valign = "center">}
    print bb/11 ; image link
    print {</td></tr></table>}
    print bb/9  ; "other information " text
    print [<BR><BR><HR>]
]
print [</td></tr></td></tr></td></tr></table>]
print [</td></tr></table></center>]
print read %footer.html

To that code, there were a number of features that I realized I should add. First, I wanted to munge email addresses so that they were less likely to get collected by spam bots. This line of code does the job well enough for my needs. It turns "name@address.com" into "name at address dot com":

(replace/all (replace bb/5 "@" " at ") "." " dot ")

I also wanted any http:// links in the "other information" section to be automatically linked. To do that, I used parse to search for "http://" and the ending space character, then wrapped that link in the required < A H R E F = ...> tags. Here's the code:

bb_temp: copy bb/9
bb_temp2: copy bb_temp
parse/all bb_temp [any [
    thru "http://" copy link to " " (replace bb_temp2 
        (rejoin [{http://} link]) (rejoin [
            { <a href="} {http://} link 
            {" target=_blank>http://} link {</a> }]))
        ] 
    to end
]

Furthermore, I wanted to have line endings in the "other information" section automatically converted to html "< b r >"s, so that they display correctly on the web page. That's easy:

replace/all bb_temp newline "  <br>  "

My friend wanted a count displayed of the total number of members. That's also easy, with "length? bbs":

print rejoin [{<font size=5> Members:  (} length? bbs {)</font></td>}]

I also added a "join now" link to the CGI page where users would be able to add themselves to the database (that page hasn't been created yet):

print {<td><a href="./add.cgi">Join Now</a></td></tr></table><BR>}

In order for users to edit/delete their info later, I needed to tag each displayed entry with a unique number to automatically select the appropriate block from the database. To do this, I added a counter variable to the foreach loop, and incremented it each time through the loop (counter: counter + 1). Then I replaced the generic edit and delete links in the code above . . .

print rejoin [{<a href="./index.cgi?function=">edit | </a>}]
print rejoin [{<a href="./index.cgi?function=">delete</a>}]

. . . with links that contain the counter, and which can be deciphered by a CGI program as "get" data:

print rejoin [
    {<a href="./index.cgi?function=edititemnumber&messagenumber=}
    counter {&Submit=Post+Message">edit | </a>}
]
print rejoin [
    {<a href="./index.cgi?function=deleteitemnumber&messagenumber=}
    counter {&Submit=Post+Message">delete</a>}
]

Here's the script, as it stands so far:

#! /home/path/public_html/rebol/REBOL -cs
REBOL []
print "content-type: text/html^/"
print read %header.html

bbs: load %bb.db

print [<center><table border=1 cellpadding=10 width=90%><tr><td>]

print-all: does [
    print {<TABLE background="" border=0 cellPadding=0 cellSpacing=0
        height="100%" width="100%"><tr><td width = "600">}
    print rejoin [{<font size=5> Members:  (} length? bbs {)</font></td>}]
    print {<td><a href="./add.cgi">Join Now</a></td></tr></table><BR>}
    print [<hr>]
    counter: 1
    reverse bbs
    foreach bb bbs [
        print [<BR>]
        if bb/1 <> "file uploaded" [
            print rejoin ["Date/Time: " bb/2]
            print "                   "
            print rejoin trim [
                {<a href=
                "./index.cgi?function=edititemnumber&messagenumber=}
                counter 
                {&Submit=Post+Message">edit | </a>}
            ]
            print rejoin trim [
                {<a href=
                "./index.cgi?function=deleteitemnumber&messagenumber=}
                counter 
                {&Submit=Post+Message">delete</a>}
            ]
            print "         "
            print {
                    <TABLE background="" border=0 cellPadding=2 
                    cellSpacing=2 height="100%" width="100%"><tr>
                    <td width = "600"><BR>
            }
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                "Name:          </FONT><strong>" bb/1 "</strong>"]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                 "Address:       </FONT><strong>" bb/3 "</strong>"]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                "Phone:         </FONT><strong>" bb/4 "</strong>"]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                "Email:         </FONT><strong>"
                (replace/all (replace bb/5 "@" " at ") "." " dot ") 
                "</strong>"
            ]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                "Age:           </FONT><strong>" bb/6 "</strong>"]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                 "Language:      </FONT><strong>" bb/7 "</strong>"]
            print [<BR>]
            print rejoin [{<FONT FACE="Courier New, Courier, monospace">}
                 "Height:        </FONT><strong>" bb/8 "</strong>"]
            print [<BR><BR>]
        ]
        ; automatically convert line endings to html " <br>"
        bb_temp: copy bb/9
        replace/all bb_temp newline "  <br>  "
        bb_temp2: copy bb_temp
        ; automatically link any urls starting with http://
        append bb_temp " "
        parse/all bb_temp [any [
            thru "http://" copy link to " " (replace bb_temp2 
                (rejoin [{http://} link]) (rejoin [
                    { <a href="} {http://} link 
                    {" target=_blank>http://} link {</a> }]))
                ] 
            to end
        ]
        print </td>
        print {<td width = "170" valign = "center">}
        print bb/11 ; image link
        print {</td></tr></table>}
        print bb_temp2
        print [<BR><BR><HR>]
        counter: counter + 1    
    ]
    print [</td></tr></td></tr></td></tr></table>]
]
print-all   
print [</td></tr></table></center>]
print read %footer.html

The page above was saved as index.cgi, and serves as the main display page for the site. In order to ensure that a fresh copy of that page is always viewed by visitors, I also created the following index.html page that simply refreshes the index.cgi page. By using that index.html page as the primary link (and by making that html file the default page for the web site), visitors always automatically see a refreshed view of the member page, with any changes/updates that have been made:

<html>
<head>
<title></title>
<META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./index.cgi">
</head>
<body bgcolor="#FFFFFF">
</body>
</html>

Next, I needed to create a form for users to enter their member information. This was saved as add.cgi. The form posts any submitted information back to index.cgi.

#! /home/path/public_html/rebol/REBOL -cs   
REBOL []
print "content-type: text/html^/"
print read %header.html

print [<center><table border=1 cellpadding=10 width=90%><tr><td>]
print [<font size=5>" Add New Member Information:"</font>]
print "         "
print "         "
print "         "
print [<hr>]
print [<FORM method="post" ACTION="./index.cgi">]
print [<br>" Your Name: " <br><input type=text size="60"
    name="username"><BR>]
print [<br>" Password (required to edit member info later): " <br>
    <input type=text size="60" name="password"><BR>]
print [<br>" Address: " <br><input type=text size="60" name="address">
    <BR>]
print [<br>" Phone: " <br><input type=text size="60" name="phone"><BR>]
print [<br>" Email: " <br><input type=text size="60" name="email"><BR>]
print [<br>" Age: " <br><input type=text size="60" name="age"><BR>]
print [<br>" Language: " <br><input type=text size="60" name="language">
    <BR>]
print [<br>" Height: " <br><input type=text size="60" name="height"><BR>
    <BR>]
print [" Other Information/Notes: " <br>]
print [<textarea name=otherinfo rows=5 cols=50></textarea><BR><BR>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Post New Member Info">]
print [</FORM>]

print [</td></tr></table></center>]
print read %footer.html

I integrated the following code into index.cgi, to read and add the info from the above form to the database:

; here's the default code used to read any data from an html form:

read-cgi: func [/local data buffer][
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    data
]
selection: decode-cgi read-cgi

; make sure at least a user name and password was entered:

if selection/2 <> none [
    if (selection/2 = "") or (selection/4 = "") [
    print {
        <strong>You must include at least
         a name and password.</strong>
        <br><br>Press the [BACK] button 
        in your browser to try again.
    }
    print [</td></tr></table></center>]
    print read %footer.html
    halt
]

; now create a new entry block to add to the database:

entry: copy []
append entry selection/2    ; name
; the time on the server is 3 hours different then our local time:
append entry to-string (now + 3:00)
append entry selection/6    ; address
append entry selection/8    ; phone
append entry selection/10   ; email
append entry selection/12   ; age
append entry selection/14   ; language
append entry selection/16   ; height
append entry selection/18   ; other info
append entry selection/4    ; password
append/only entry [
    {<a href = "./default.jpg" target=_blank>
    <IMG align=baseline alt="" border=0 hspace=0 src="./default.jpg"
    width="160" height="120"></a>}
]

; append the new entry to the database, and notify the user:

append/only tail bbs entry
save %bb.db bbs
print {<strong>New Member Added.</strong>
    Click "Edit" to upload a photo.}
print [</td></tr></table></center>]
print read %footer.html

; now display the member page with the new info refreshed:

wait :00:04
refresh-me: {
    <head><title></title>
    <META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./index.html"></head>
}
print refresh-me
quit

Now we can finish up the rest of the work in step 3 of our outline. The pseudo code in my outline reads "The pages where the users edit their information will be forms that display the information currently in the selected entry, without the password. When the user submits the new password and updated info, the CGI checks that the submitted password matches the existing password for that entry, and then replaces the old block with the new one, in the database text file". I've already created links in index.html to reference the "edititemnumber" (created earlier using a counter variable in the foreach loop of index.cgi). And we've already created the basic data entry form to add new users. So we can check for the edititemnumber, and fill the form with appropriate items from the database. In order to find and replace the original entry in the database, once the user has made changes, the original values also need to be submitted as additional hidden form fields, along with the user-editable values in the form's text fields. Here's what I came up with:

if selection/2 = "edititemnumber" [
    ; pick the correct entry from the database, using the submitted
    ; counter variable from the "edit" link in index.cgi:
    selected-block: pick bbs (
        (length? bbs) - (to-integer selection/4) + 1
    )
    print [<font size=5>" Edit Your Existing Member Information:"</font>]
    print "         "
    ; here's a link we'll need for the section of the outline that
    ; enables image uploading:
    print rejoin [
        {<a href="./upload.cgi?name=} first selected-block 
        {">Upload Image (Add or Change)</a><hr>}
    ]
    print "         "
    print "<br><br>"
    print {<strong><i>PASSWORD REQUIRED TO EDIT! </i></strong> 
        (Enter it in the field below.)}
    print "<br><br>"
    print [<FORM method="post" ACTION="./edit.cgi">]
    print rejoin [
        {<br> Your Name:  <br>
            <input type=text size="60" name="username" value="}
         first selected-block {"><BR>}
    ]
    print [<br> <strong> " Member Password " </strong> "(same 
        as when you created the original account): " <br>
        <input type=text size="60"  name="password"><BR>
    ]
    print rejoin [
        {<br> Address:  <br><input type=text size="60" 
            name="address" value="} 
        pick selected-block 3 {"><BR>}
    ]
    print rejoin [
        {<br> Phone:  <br><input type=text size="60" 
            name="phone" value="} 
        pick selected-block 4 {"><BR>}
    ]
    print rejoin [
        {<br> Email:  <br><input type=text size="60" 
            name="email" value="} 
        pick selected-block 5 {"><BR>}
    ]
    print rejoin [
        {<br> Age:  <br><input type=text size="60" 
            name="age" value="} 
        pick selected-block 6 {"><BR>}
    ]
    print rejoin [
        {<br> Language:  <br><input type=text size="60"
            name="language" value="} 
        pick selected-block 7 {"><BR>}
    ]
    print rejoin [
        {<br> Height:  <br><input type=text size="60" 
            name="height" value="} 
        pick selected-block 8 {"><BR><BR>}
    ]
    print [" Other Information/Notes: " <br>]
    print [<textarea name=otherinfo rows=5 cols=50>]
    print [pick selected-block 9]
    print [</textarea><BR><BR>]
    print rejoin [
        {<input type="hidden" name="original_username" value="}
         pick selected-block 1 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_date" value="}
        pick selected-block 2 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_address" value="}
         pick selected-block 3 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_phone" value="}
        pick selected-block 4 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_email" value="}
        pick selected-block 5 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_age" value="} 
        pick selected-block 6 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_language" value="} 
        pick selected-block 7 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_height" value="} 
        pick selected-block 8 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_otherinfo" value="} 
        pick selected-block 9 {">}
    ]
    print [<INPUT TYPE="SUBMIT" NAME="Submit" 
        VALUE="Update Member Information">]
    print [</FORM>]
    print [</td></tr></table></center>]
    print read %footer.html
    quit
]

I added the above code to index.cgi. Notice that the above form points to edit.cgi, which actually does the work of checking the password and processing the changes in the database. It has all the standard header and read-cgi code, and then it uses a foreach loop to look for a database entry that has all the same data as that submitted by the hidden items in the form above, and checks the original password in that entry. In comparing the original password with that entered by the user, I also enabled an administrator password "blahblah". I also added the code to email users their password, in case they've forgotten it (just send the stored password to the email address contained in the database, for that entry):

#! /home/path/public_html/rebol/REBOL -cs   
REBOL []
print "content-type: text/html^/"
print read %header.html

bbs: load %bb.db

read-cgi: func [/local data buffer][
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    data
]
selection: construct decode-cgi read-cgi

; get password from the entry submitted:

foreach message bbs [
    if all [
        find message selection/original_username 
        find message selection/original_date 
        find message selection/original_address 
        find message selection/original_phone 
        find message selection/original_email 
        find message selection/original_age 
        find message selection/original_language 
        find message selection/original_height 
        find message selection/original_otherinfo
    ] [read-pass: message/10]
]
; save the old block:

old-message:  to-block reduce [
    selection/original_username 
    selection/original_date 
    selection/original_address 
    selection/original_phone 
    selection/original_email 
    selection/original_age 
    selection/original_language 
    selection/original_height
    selection/original_otherinfo 
    read-pass
]
; so that the original pass is not replaced by "blahblah":

either selection/password = "blahblah" [
    entered-pass: read-pass
] [
    entered-pass: selection/password
]
; create the new entry for the database:

new-message:  to-block reduce [
    selection/username 
    selection/original_date
     selection/address
     selection/phone
     selection/email
     selection/age
     selection/language
     selection/height
     selection/otherinfo
     entered-pass
]
; check the password, and replace:

if selection/password <> "" [
    either (
        read-pass = selection/password
    ) or (
        selection/password = "blahblah"
    ) [
        foreach message bbs [replace message old-message new-message]
    ] [
        print {
            <strong>Forgot your member password?</strong> <br><br>
            It's being emailed to the address for this entry, right now...
            Wait for this page to refresh, then <strong>check your email!
            </strong>
        }
        print read %footer.html
        wait 3
        set-net [user@website.com smtp.website.com]
        send (to-email selection/original_email) (to-string rejoin [
            "Forgot your member password?" newline newline 
            trim {Someone was editing an entry with this email address, 
                but the incorrect password was used.  Here is the correct
                password, in case you've forgotten:}
             newline newline read-pass
        ])
    ]
]
save %bb.db bbs

; diplay the edited results on the main user page:

refresh-me: {
    <head><title></title>
    <META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./index.cgi"></head>
}
print refresh-me
print read %footer.html

Here, I decided to add the backup code. What I did was create a folder for all previous versions of the database text file to be saved as backups. Then I created a text file that contained the number of the current backup file (to start out, that text file just contained the number 1). Then, I incremented that number and saved it back to that number file. And finally, I saved a copy of the current database to a text file with the current backup number appended to the filename. This code went right before bb.db was saved in the CGI above:

backup-num: load %backup-num.txt
backup-num: backup-num + 1
write %backup-num.txt backup-num
filename: to-file rejoin ["./backup/bb-" (to-string backup-num) ".txt"]
save filename bbs

The following code is basically a simpler version of the editing code above, which allows users to delete an entry. All that's needed in this case is the username and password. All the other info is passed along to delete.cgi as hidden fields. This code gets added to index.cgi:

if selection/2 = "deleteitemnumber" [
    selected-block: pick bbs (
        (length? bbs) - (to-integer selection/4) + 1
    )
    print [<font size=5>" Delete An Existing Member Account:"</font><hr>]
    print [<FORM method="post" ACTION="./delete.cgi">]
    print rejoin [
        {<br> Your Name:  <br>
            <input type=text size="60" name="username" value="} 
        first selected-block {"><BR>}
    ]
    print [<br>" Member Password (
        same as when you created the original account): " 
        <br><input type=text size="60"  name="password"><BR><BR>
    ]
    print rejoin [
        {<input type="hidden" name="original_username" value="} 
        pick selected-block 1 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_date" value="} 
        pick selected-block 2 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_address" value="} 
        pick selected-block 3 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_phone" value="} 
        pick selected-block 4 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_email" value="} 
        pick selected-block 5 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_age" value="} 
        pick selected-block 6 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_language" value="} 
        pick selected-block 7 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_height" value="} 
        pick selected-block 8 {">}
    ]
    print rejoin [
        {<input type="hidden" name="original_otherinfo" value="} 
        pick selected-block 9 {">}
    ]
    print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Delete Member Info">]
    print [</FORM>]
    print [</td></tr></table></center>]
    print read %footer.html
    quit
]

Here's the code for delete.cgi, which the above form points to, and which does the actual work of deleting the selected block from the database (it's basically a variation of the edit.cgi script above):

#! /home/path/public_html/rebol/REBOL -cs   
REBOL []
print "content-type: text/html^/"
print read %header.html

bbs: load %bb.db

read-cgi: func [/local data buffer][
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    data
]
selection: construct decode-cgi read-cgi

foreach message bbs [
    if all [
        find message selection/original_username 
        find message selection/original_date 
        find message selection/original_address 
        find message selection/original_phone 
        find message selection/original_email 
        find message selection/original_age
        find message selection/original_language 
        find message selection/original_height 
        find message selection/original_otherinfo
    ] [read-pass: message/10] 
]

old-message:  to-block reduce [
    selection/original_username 
    selection/original_date 
    selection/original_address 
    selection/original_phone 
    selection/original_email 
    selection/original_age 
    selection/original_language 
    selection/original_height 
    selection/original_otherinfo 
    read-pass
]

if selection/password <> "" [
    if (
        read-pass = selection/password
    ) or (
        selection/password = "blahblah"
    ) [ 
        backup-num: load %backup-num.txt
        backup-num: backup-num + 1
        write %backup-num.txt backup-num
        filename: to-file rejoin [
            "./backup/bb-" (to-string backup-num) ".txt"
        ]
        save filename bbs

        foreach message bbs [replace message old-message ""]
    ]
]

remove-each message bbs [
    any [
        message = [""] 
        (all [
            message/1 = "" message/2 = "" message/3 = "" message/4 = ""
            message/5 = "" message/6 = "" message/7 = "" message/8 = ""
            message/9 = ""
            ]
        )
    ]
]

save %bb.db bbs

refresh-me: {
    <head><title></title>
    <META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./index.cgi"></head>
}
print refresh-me
print read %footer.html

Creating the image upload page for step #4 in our outline was a bit of a challenge. That's because REBOL has no built-in way to accept binary data from html forms (images, in this case), called "form-multipart" data. I searched the mailing list and quickly found a solution at http://www.rebol.org/cgi-bin/cgiwrap/rebol/ml-display-thread.r?m=rmlKVSQ. Andreas Bolka's "decode-multipart-form-data" did exactly what I needed. That function converts the data entered by a user, as well as the files they choose and upload from their hard drive, into a friendly and easy to use REBOL object.

#! /home/path/public_html/rebol/REBOL -cs
REBOL [Title: "HTTP File Upload"]
print "content-type: text/html^/"
print read %header.html

read-cgi: func [/local data buffer][
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    data
]

; here's Andreas's magic function to read form/multipart data:

decode-multipart-form-data: func [
    p-content-type
    p-post-data
    /local list ct bd delim-beg delim-end non-cr non-lf non-crlf mime-part
] [
    list: copy []
    if not found? find p-content-type "multipart/form-data" [ return list ]

    ct: copy p-content-type
    bd: join "--" copy find/tail ct "boundary="
    delim-beg: join bd crlf
    delim-end: join crlf bd

    non-cr:     complement charset reduce [ cr ]
    non-lf:     complement charset reduce [ newline ]
    non-crlf:   [ non-cr | cr non-lf ]
    mime-part:  [
        ( ct-dispo: content: none ct-type: "text/plain" )
        delim-beg ; mime-part start delimiter
        "content-disposition: " copy ct-dispo any non-crlf crlf
        opt [ "content-type: " copy ct-type any non-crlf crlf ]
        crlf ; content delimiter
        copy content
        to delim-end crlf ; mime-part end delimiter
        ( handle-mime-part ct-dispo ct-type content )
    ]

    handle-mime-part: func [
        p-ct-dispo
        p-ct-type
        p-content
        /local tmp name value val-p
    ] [
        p-ct-dispo: parse p-ct-dispo {;="}

        name: to-set-word (select p-ct-dispo "name")
        either (none? tmp: select p-ct-dispo "filename")
               and (found? find p-ct-type "text/plain") [
            value: content
        ] [
            value: make object! [
                filename: copy tmp
                type: copy p-ct-type
                content: either none? p-content [none] [copy p-content]
            ]
        ]

        either val-p: find list name
            [change/only next val-p compose [(first next val-p) (value)]]
            [ append list compose [ (to-set-word name) (value) ] ]
    ]

    use [ ct-dispo ct-type content ] [
        parse/all p-post-data [ some mime-part "--" crlf ]
    ]

    list
]

; now we can put the uploaded binary, and all the text entered by the
; user via the html form, into a REBOL object.  we can refer to the
; uploaded photo using the syntax:  cgi-object/photo/content

post-data: read-cgi
cgi-object: construct decode-multipart-form-data (
    system/options/cgi/content-type copy post-data
)

; I created a "./files" subdirectory to hold these images.  Now
; write the file to the web server using the original filename,
; but without any Windows path characters, and notify the user:

adjusted-filename: copy cgi-object/photo/filename
adjusted-filename: replace/all adjusted-filename "/" "-"
adjusted-filename: replace/all adjusted-filename "\" "-"
adjusted-filename: replace/all adjusted-filename " " "_"
adjusted-filename: replace/all adjusted-filename ":" "_"
adjusted-filename: to-file rejoin ["./files/" adjusted-filename]
write/binary adjusted-filename cgi-object/photo/content
print [<strong>]
print {Upload Complete.  }
print [</strong>]
print [<br><br>]

; now add an html link to this file, to the database:

bbs: load %bb.db    
entry: copy []
link-added: rejoin [
    {<a href = "} to-string adjusted-filename {" target=_blank>}
    {<IMG align=baseline alt="" border=0 hspace=0 src="} 
    to-string adjusted-filename 
    {" width="160" height="120">} </a>
]  ; display image inline
append entry link-added
foreach message bbs [
    if (all [
        cgi-object/username = message/1 
        cgi-object/password = message/10
    ]) [
        if ((length? message) < 11) [append message ""] 
        message/11: entry
    ]
]
save %bb.db bbs

; show additions by refreshing the index.cgi page:

refresh-me: {
    <head><title></title>
    <META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./index.cgi"></head>
}
print refresh-me
print read %footer.html

The last step in the outline was easy. I just used the code from the previous case study (the password protected CGI text editor), and pointed it to the database text file. I also looped through the backup directory and printed links to each of the files in that directory, so that any of the previous backup files could be easily copied and pasted into the editor, to revert the database to a previous state.

#! /home/path/public_html/rebol/REBOL -cs   
REBOL []
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Edit Database!!!"</TITLE></HEAD><BODY>]

read-cgi: func [/local data buffer][
    switch system/options/cgi/request-method [
        "POST" [
            data: make string! 1020
            buffer: make string! 16380
            while [positive? read-io system/ports/input buffer 16380][
                append data buffer
                clear buffer
            ]
        ]
        "GET" [data: system/options/cgi/query-string]
    ]
    data
]

selection: decode-cgi read-cgi

; if schedule.txt has been edited and submitted:

if ((selection/2 = "save") or (selection/2 = "save")) [ 
    ; save newly edited schedule:
    write %./bb.db selection/4
    print ["Database Saved."]
    ; print {<META HTTP-EQUIV="REFRESH" CONTENT="0; URL=./bb.db">}
    quit
]

; if user is just opening page (i.e., no data has been submitted 
; yet), request user/pass:

if ((selection/2 = none) or (selection/4 = none)) [
    print [<strong>"W A R N I N G  -  Private Server, Login Required:"
        </strong><BR><BR>]
    print [<FORM ACTION="./editor.cgi">]
    print [" Username: " <input type=text size="50" name="name"><BR><BR>]
    print [" Password: " <input type=text size="50" name="pass"><BR><BR>]
    print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
    print [</FORM>]
    quit
]

; check user/pass, end if incorrect:

response: false
if ((selection/2 = "username") and (selection/4 = "password")) [
    response: true
]
if response = false [print "Incorrect Username/Password." quit]

; if user/pass is ok, go on (backup before changes are made):

cur-time: to-string replace/all to-string now/time ":" "-"
schedule_text: read %./bb.db
write to-file rejoin [
    "./backup/" now/date "_" cur-time ".txt"
] schedule_text

; here's the form that lets the user edit the text:

print [<center>]
print [<strong>"Be sure to click [SUBMIT] when done:"</strong><BR><BR>]
print [<strong>"(This will OVERWRIGHT the current database!)"</strong>
    <BR><BR>]
print [<FORM method="post" ACTION="./editor.cgi">]
print [<INPUT TYPE=hidden NAME=submit_confirm VALUE="save">]
print [<textarea cols="100" rows="25" name="contents">]
print [schedule_text]
print [</textarea><BR><BR>]
print [<INPUT TYPE="SUBMIT" NAME="Submit" VALUE="Submit">]
print [</FORM>]
print [</center>]
print {<br><br><br><br><br><br><br><br><hr>}

; here's a linked listing of all the backup files available for
; copy/paste:

foreach file (read %./backup/) [
    print rejoin [
        {<a href="./backup/} file {" target=_blank>} file {</a> }]
    ]
print [</BODY></HTML>]

That's it - the web site and all its features are complete! You can see a live demo at http://guitarz.org/tester and download the complete set of scripts in this case study at http://guitarz.org/tester/member_board.zip.

31.11 Case 10 - A CGI Event Calendar

My friend liked the system above so much that we adapted it for use as an online classifieds page and also as an event calendar listing on the same web site. For the calendar, we just changed the database fields to: Event, Date/Time, Location, Contact Name, Contact Phone, Contact Email, Requirements. Links and display text such as "Join Now" were simply changed to "Enter A New Event", etc.

The calendar was in use for quite a while and functioning beautifully, when my friend asked if I could create an event page that actually looked like a normal calendar display, instead of just a list of events. Ok, so here's how I broke down the basic creative process:

  1. Design an html page that looks like a calendar. My guiding thought was that the CGI program which printed this page would include a loop that runs through the days of the current month, and prints html table rows and cells for each numbered day, one row per group of days Sunday-Saturday.
  2. For each day of the month printed in the table above, search through the database for dates that match the current table cell being printed, and then print the event description (first item in the block for that event), with a link to the event listing page.

As always, I began the process by looking for some existing code that may be useful in my design (it's always a good idea to avoid reinventing the wheel). My work was immediately made easy, when I searched for "calendar" at rebol.org. I quickly found the HTML calendar by Bohdan Lechnowsky, which prints out an html calendar display for the current month. It uses a table design created by loops, much like I had imagined. I read through Bohdan's code, made some comments as to what each section accomplished, and made some changes to the design of the tables so that the calendar stretched to fit the entire page of the browser. I also wrote a line of code to visually highlight the current day (so that today's date is always printed in a unique color). You can see the original code at the link above, and here are my tweaks and comments:

#! /home/path/public_html/rebol/REBOL -cs   
REBOL []
print "content-type: text/html^/"
print {<HTML><HEAD><TITLE>Event Calendar</TITLE></HEAD><BODY>}

; print month + year header:

date: now/date
html: copy rejoin [
    {<CENTER><TABLE border=1 valign=middle width=99% height=99%>
        <TR><TD colspan=7 align=center height=8%><FONT size=5>}
    pick system/locale/months date/month {  } date/year
    {</FONT></TD></TR><TR>}
]

; print days header:

days: ["Sun" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat"]
foreach day days [
    append html rejoin [
        {<TD bgcolor="#206080" align=center width=10% height=5%>
        <FONT face="courier new,courier" color="FFFFFF" size="+1">}
        day 
        {</FONT></TD>}
    ]
]
append html {</TR><TR>}

; print non-month days at the begining of month in gray:

sdate: date  sdate/day: 0  
loop sdate/weekday // 7 + 1 [append html {<TD bgcolor=gray></TD>}]

; print every other day, with the current day in a unique color:

while [sdate/day: sdate/day + 1 sdate/month = date/month][
    append html rejoin [
        {<TD bgcolor="#}
        ; I ADDED THIS CODE TO VISUALLY HIGHLIGHT THE CURRENT DAY:
        either date/day = sdate/day ["AA9060"]["FFFFFF"]
        {" height=14% valign=top>} sdate/day
        {</TD>}
    ]
    if sdate/weekday = 6 [append html {</TR><TR>}]
]

; print non-month days at the end of month in gray:

loop 7 - sdate/weekday [append html rejoin [{<TD bgcolor=gray></TD>}]]

; finish and print:

append html {</TR></TABLE></CENTER></BODY></HTML>}
print html

With step 1 in my outline done, I completed the second and last step by adding the code below. It was really simple. First, I created a variable called "event-labels" which would hold any events in the database that occurred on a given day. I put this inside Bohdan's while loop, which ran through each day of the month and printed the calendar table cells for each separate day). I used a foreach loop to compare each date found in the database to the current date being added to the calendar. If there's a match, "event-labels" is rejoined with the first item in the event entry (the description of the event), and linked to the event display. The string of text in event-labels is then later printed into the table, within the current day's cell.

while [sdate/day: sdate/day + 1 sdate/month = date/month][
    event-labels: {}
    foreach entry bbs [
        date-in-entry: 1-Jan-1001
        attempt [date-in-entry: (to-date entry/3)]
        if (date-in-entry = sdate) [
            event-labels: rejoin [
                {<font size=1>}
                event-labels
                "<strong><br><br>"
                {<a href="http://website.com/path/calendar">}
                entry/1 
                {</a>}
                "</strong>"
                {</font>}
            ]
        ]
    ]

That's it! Here's the whole script:

#! /home/path/public_html/rebol/REBOL -cs
REBOL []
print "content-type: text/html^/"
print {<HTML><HEAD><TITLE>Event Calendar</TITLE></HEAD><BODY>}

bbs: load %bb.db
date: now/date
html: copy rejoin [
    {<CENTER><TABLE border=1 valign=middle width=99% height=99%>
        <TR><TD colspan=7 align=center height=8%><FONT size=5>}
    pick system/locale/months date/month {  } date/year
    {</FONT></TD></TR><TR>}
]

days: ["Sun" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat"]
foreach day days [
    append html rejoin [
        {<TD bgcolor="#206080" align=center width=10% height=5%>
        <FONT face="courier new,courier" color="FFFFFF" size="+1">}
        day 
        {</FONT></TD>}
    ]
]
append html {</TR><TR>}

sdate: date  sdate/day: 0  
loop sdate/weekday // 7 + 1 [append html {<TD bgcolor=gray></TD>}]

while [sdate/day: sdate/day + 1 sdate/month = date/month][
    event-labels: {}
    foreach entry bbs [
        date-in-entry: 1-Jan-1001
        attempt [date-in-entry: (to-date entry/3)]
        if (date-in-entry = sdate) [
            event-labels: rejoin [
                {<font size=1>}
                event-labels 
                "<strong><br><br>"
                {<a href="http://website.com/path/calendar">}
                entry/1 
                {</a>}
                "</strong>"
                {</font>}
            ]
        ]
    ]
    append html rejoin [
        {<TD bgcolor="#}
        either date/day = sdate/day ["AA9060"]["FFFFFF"]
        ; HERE, THE EVENTS ARE PRINTED IN THE APPROPRIATE DAY:
        {" height=14% valign=top>} sdate/day event-labels
        {</TD>}
    ]
    if sdate/weekday = 6 [append html {</TR><TR>}]
]

loop 7 - sdate/weekday [append html rejoin [{<TD bgcolor=gray></TD>}]]

append html {</TR></TABLE></CENTER></BODY></HTML>}
print html

31.12 Case 11 - A Backup Music Generator (Chord Accompaniment Player)

In my music lesson business, one of the things we teach is improvisation and "jam session" skills. In order to practice, students need to be able to play along with recordings of various chord progressions. Serious musicians traditionally purchase album recordings designed specifically for that purpose. A significant industry exists to provide such "backing track" recordings, but for beginning students to experiment with basic improvisation skills, I decided to create a simple program they could use to hear and play along with a wide variety of chords, at different tempos, in all 12 of the musical keys. Building such a program with REBOL was easy. Designing an application to play pre-recorded chords from a given text list took less than a half hour.

Here's the basic outline I came up with to get started (a very basic knowledge of chord notation is required for this case study):

  1. Record wave files of major, minor, dominant 7th, half diminished, diminished 7th, minor 7th, and major 7th chords on all 12 root notes (A, A#, B, C, C#, D, D#, E, F, F#, G, and G#), along with a few other commonly used chord voicings. The recordings all needed to be of short block chords, of the exact same duration and volume.
  2. Compress and embed the wave files using the binary embedder (our old friend :).
  3. Load each sound into memory and give each one a variable label.
  4. Create a GUI with text fields for the chords to play, and the tempo. Add "play" and "stop" buttons to control the action.
  5. When the "play" button is clicked, play the wave data for each chord in the given progression, using the given timing gap. There will need to be some multitasking code to enable the looping chord progressions to be stopped.
  6. Add some buttons to save and load the chord progressions, along with a button to provide some help/instructions.

The first step was mechanical - no programming required. I recorded the sounds of all twelve major, minor, and dominant 7th chords using my favorite recording software and my guitar. I saved each sound as a separate wave file in 1 directory on my hard drive (I later recorded a much larger collection of chords, but this was enough to get started).

For step 2 in the outline, I used a variation of the binary embedder program from the earlier freecell case study to loop through the files in the directory:

REBOL []

system/options/binary-base: 64
sounds: copy []
foreach file load %./  [
    print file
    uncompressed: read/binary file
    compressed: compress to-string uncompressed
    if ((length? uncompressed) > 5000) [
        append sounds compressed
    ]
]
editor sounds

It provided one huge block of data containing every one of those sounds in embedded format. Here's an example of what 1 chord's data looked like:

64#{
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
izzOImBS0XXDL6y46GdOqbBt0BOrPs1/7RYjfZQTcjLnzOKCqKf/U6dZj7533OUw
Fa6aeaYr5HLP4ik/7ecRH0qOD14w3xXO5q7pjP+HKHf88zcr74vxxcrfi9h2wrXY
r+CRIaZ6fBSsdia4Em+jQeNBIJCd0eGl9SvJVG5W28q+z4YFor5hZbeOQ+SkVtKr
7xdeedX4/m0ruGlRy4+Rj4LjzQ84TZyi0swyH7ahZ6vcV6MTxGXLnqXlispgDf3t
+I7H1WtKvAu/VTujBWghsZk6D7HUcZmOLUiaAPDbA1+7vPSL8yl3PeD4BORLi7Dv
9RD4c4IAj9vYYlZQ2KLQBpf9cRd9aHozRGs/eT0D0cnG3cO7/5v/N24wPMbtEDnN
Z93S/D07V1Omr9+2/NiZIwuP5a1+Fs9NS98369iuzIRj3scijhVlrtxycuHWnYHH
TmXaHEMfox3+m3Z64dHkKzGwubsCfjjA7Q6HLVrVd5B8bNlRi2OWm3bvvHqoY35M
tLvvYs8Ev+u2O3xLvZ654iNfen9DJpgHnfqoh21HFyVFVYSwo5OmjS7IC2S4jLgW
mz7LtaJt7e3CLcKXlc8rlrS8K0gomt5t3VE9lNhv1kjGKnouClZJV8p76zXN2qbw
NrxktNeJX9AzXiOpn9Mo/5L1b3FPa88l1QxzO9Rg02h13x3u7u05K2RLkLXfF6vT
3jetv5KIYHVzY3dw3/hwii6qx4//ArEXYZC4ySFmoIP8I+bzr+spj0TFCQ2x7NT2
m1xZ8aIXT7L+Nqxq2Cy7BAZxjb2aNkPB3s5NTbMKXwmiQSxtLcnJHWzjzPBjpgYO
TxF6sWa2z3w2vSH97qzcxLVJZ5KHlqasfDGlgJVkcQQfGIwN6fG65jPfkoLrsj2H
7OxXFWrqxxTHyHDvT1MfzV8R2eyVEKlKbE09HLXc73JwdcA/h0Rs6dhlOUd+S0Cu
+NZ6uhGbE//i1tc/7zffnHU/41T4y0fiy4L4/FbVE5SLaivh06i+DVlm+fXBt+au
OaBbOrr+qyZX2FOXkT386sFj3of17AnBIbFMSq/j/TxVWVOc9X3wRcOn/Mp3qtXQ
zuHZIteJWtCGvuktrJKYN4SHt17z2LVDjYZ0gjfi49goPJz8ziKCXktfzhgLWhd4
m3XGLtp/9fRXof+cKqPXrhTPbYu7u3zNfJvQm974oIzg+cQXxIt2FxwcA8KmbtjA
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
fMM9IKlYXY657r8CM2dcWBy5/dnhc5n2mYsyjUebd/9bD14Tu6FmU1CG76J/sxpj
IGE8n092FvQkyz2EZlyA5Tm7/W50z2CvTC+d/T1qMSPZ7qSt0PqrwzQnsj3H1tdO
ayO3E7jsdF7oQvJa5JXi+yoA47nY8Y/9cVuM7Q97vEOCjYfdfRYfWwp9A3psFOlm
6pIVLrIWZaUst39V2+qGpMnu2r/uW525aW6ztNmyJ0CULOzhfKxpLztWOre4rKyR
Ta0fqu9sieMAnbmddzuV/YtF6mGWYrHm6thUraPcdfCgYJl8nQEEEaC/0kJtwxzG
mEqMP7oS8wl2BMUk76dn2+1zDXAV2+4meqO6EC+M1xRBUposTKkeW2nIMOwy3Qbl
gx+DEkFr4WTLK46jPrtDeiPcwyI9xax43L2J6SPXJA/VGpAd9ijhjWWKk8mPHfkv
zmdWc0x9SJ1vjssr+6VMJxbF7oPdBcozFBaWPCaRH9YlmvvgXcQfZAEuHHuOAOD/
kEewKTCEmWt6h0g3DhofAIPmDwaNqQk9FXuGrMK+B1Yb2zVWQ/d6r/ajBq+LR+Wj
464QEOyj8eRoxdDK/n3tBTW36192/+EH8662vWlZ2DSl7ljFjjJFydHSvNLH/34W
fPh7Ot+v2Kd2Y+ONtt1dj7sPdiX1y/sfd+e2HWr4Xh1R8bMKVRb7T5C/5NfD74yv
0dl5WVd/sP8w8gcLdhafbNzUf0hUL+NoO8dmTKraSRgHZ4uzs0gmn7G1d8y3ukpq
AJlVObJA2Sd5gfgMv2rgjPiIcuPEfPB3mBdqFNoFaoPD6BWuAr+noVbhxcHDfts9
Ntlz7b295wdjwmdOWzJjXqL1zD3JxWn4OXdSy5fKVz1dKEu5n/gzOs4HRrOGV4y6
yS1USlmliCmo5s1tO9l4qK6h2qIupsGxCddqy7nY8bOzut2ipa4uufDyL0pBXJmk
VtcY1Haz42aHZ3d7/+VBpHi+RCCnqrXKHVI/RYDygSJMU6fxHltl8gLogCfcluDF
fG3Fp8lx7ZgUchMryDqUGWR1lTXH0pP0g/6YfsFiH7jGrAHdRlXjK2llDgtcljh4
212077ULtEFau9nU22FcDjpasObS7Vg76DTMXKOr9q0yZXiJCFCpDUWICfx9zGOM
C5xofgmuRBWi9kP0ZobpBagdthocDuRP2CmNQrnwqFg67CiePjxHmi9/Jy+V/Fa8
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
DKSOZNVzH/ArfK74POEr+ed8rnBt7GYmn6mju2n/UMuhT9QoGITdgABXAEOgDMoU
pol5nVnGW8lfy30uyPaPEz0RF4Vejbwc2SrJiZsTPzx6VsStiIColdFnw+8EPwy7
EnU/hh37J/5+yvj0WT2u9/T0eJFBdjvRhexS0C27W98uQPKRhM2JK1KGduvTo2uf
zoGbh6WMGjny99D5g9bmTu1VnLk5bXZKSGL/eEHsXklL2O+Q2JA1Ic6gpYErRBcC
0wO7BapEcaLrAV38qv32+Qf5iXznCdz8dE4u8wLjMXUu4PQcsL+2bDREGDdYR9tw
a4attyXfvNuMGRj6Y4bPhkO6aVq+Lkx/ydseleZ7Jqrpo7HU2GI4rWNqVKqWDlP7
u87pndmd99QF2rX6Fdqe6gkdDzv46v7qA5pqzUTtTR1bf8DY33LaVuU8776C9MJ6
Ih53gFtqXWoWm3MNDGOiqcWA6pfoJFqtbrPph+mdkW5YpEaVF+TL2163BsoHtb/q
6KcaoTmh/auze0dZu1kJK562+kqLGkfUK+t/N81ovtxU0TK+DVHWdgg12/Vco8NE
sQ51fHZPc4OOrzbAdsk8wvTZUuSSYt1ID7kbXki/yMSYx5k9OV+5jezLjBX03cwd
HBO3ivua85v9gnOCO4Z3hu/r+1W4xm+vf5l/up9VuM5/q/hp+DvJvGgypir2r6SB
adszArvf7Ha0W1jXo0l3o/8LWyiuEh7kT+VomRW0C/B96DjUH3CQFEJH1JLJkJ36
gdJAK6IMpYBUjIwlTmN+nl7IGNTuVLgOYPlob88n92n3Wedyq9460V5geWcsNKfb
IMdd2yhzlmW03deZ6Lhp7WduMj01lRjvGd5qdZ0eVaxmviHZsshhdhtRHTYevY0O
JdjQBNgKHYZ6gEHkWrTQ3ccx0+Iyxhuf64doT3di7cL2NOVB+ZrWc1KL1CabKr/V
fkn1n/q15qPGpB1iUJhb7MfdPMyOryP3EYMwH/QfT5PrsAfGJ4HFlKUMM3syd6mg
tyBZsNx3rU+WT6NAL3giAH1/CxqFcaJZIrnfWz9pQHBgZkCxPxrwKZAlniHqJ/aE
6MOeBzPFz4OKw6ZHOsKnhrEiO2JOx7MSryasiKmKCo65F/1WcjZ0Y/Ck4M+idf4H
/E8FoIG+4quBM4IZwYdF0/yrhaiPj0+A4II3Y45zXjLFtGTKAsoN6mVaX0oSsJQ8
C0aAdNJN9CdwHCVCyf5EB5aFPUFXYtWYEhuF++I8fBAZBd4nC8lb4CeYQe1NaYDa
qMMZt6hf4P7UDfTuDBV1BbwCArzPWYoBmATvg1/FQvDn5N/gQ3IKHoH1QfOQIA/m
3Gc7b+1he2W9YSuxw45S1yIslhwAeIDLUB7tFovLq/VZJBT5vRfM4eVzrrE+0odR
YcolSAhWA42gAnLDYylJsArYT74jYGIyORlspnDogcydzGTWU85LjoLbgxvLa+WO
Yq9l/8u0MSO4kbxA/l6fQN5wTjvXyaGwC1hTWUHsOaxFrE6OmvuUu4t7ljuAR+FS
WMfp22gf6QStjDKbMp4yijIDngVthaRgO7mJ9IcG0BzMBlYRax4niKtjDaOvpxyG
q6Bp4D6CiZKupfYu5oc6mvp7xyclpNC1VbVtlucochQhSoUyqv2G6r7GpTcYv5tu
W0JsdfbVToa73f3Ko0bD8AVEAUCA8XAWhUmxURBKAc1Di6MHMnK8OU3jjudH+xh5
Z3zm8bZxhnF/swRMEeMVdRFVRn/JTGTE0lczrMxjzHPMF+x4TijrK30ZYxDzHdOH
YaJMo+RT/Wlb4BxoF/ycUk53MZyMnYwDNDbtOS2EPoFmpeyBJoA7gav4NiwIu4W9
IX7j3bAhCOI4bz9mrTKkaos6H3U8VYqVbMXytqXS9OYvjUPqtXVD6ubUueo7mnZK
g1v95IDiseKH8ntHinqD5oNmlHaF/qwh1lhlPGwUGwzaA9qX2lpNimaZ9pLuqG61
DtXtNQQZlxpm6HFDiGmrfo/2jgbpnNnxW8lWPlDe7OirqlWdVXfRGQz9zaXW9Y6n
7gnoJmwXPp7cBH2mBtJP0F8xVrBKWN+YONPNzGCGM/9mnKO9pVyAOKCJXEPqgCbw
GbSQ0o8+izmMdZEj5El5/fir+Cv44/iF/Ev8u4K/hIeEpN8j0XnRusANgfEBBQEx
AVsCGkXposOi6eIq8VTxCvE3cWbwhNCqUHtodFiP8LYIRaRZkh2zLKYk2i+WHauK
TYr7Eh0tASWSqCGSvEheJBp1Kf5U4sfEx4ntiTOTEO/35JQvqatTnUnhCRFxnJip
ksrwW6HhwS9EDb6hvDD2egZBbYXPQ0+gTtAP1JKHyHjyGv4Vy8HS8DV4HZaKfcHk
+He8ANMhh1ABOh4ZiCSgTAxD61APakS12Gm0B1aLFHvuuVc49M4i2yD7I3u8RWZj
2pbZlrrGOSMcSZ7RXgLPcF3wzHHtdaxyks4U+1TbFofEtcxRYiMd9e4a7/+22svc
TLyQuI5PIK6QJLDFq+UW6mxKODQPYAAPofvUTdQMihXyBZqwJchINJ+YAjiAwcAv
rwcfQSKdMkux6a6e6PRrz5Dvb7W0prbNlrOUbBXUmdn5Wk3TdNc/14PGHOsWy05z
P+tVa4LVaXXbJjqOOZNcoNvjzkct2Er0O/qDjIW44BHiAeEHVJFRhB7dg77HqMRa
bL9H7T6MbMCvEPPJ/5FW8g04kdadXc4dJggTYn7LAtJEp7xctSQwTswL9g3ZHHw3
qLfYLZrjv8X3uaCUD3MHs5uYMxnXGJuYCMOXsY7hZA5jbqD/R8uhjfPubA90CKwD
Z0E06CS4EewPpkJbwTzgIFlKHMNPo32QNM9VN9X9xnXYFe265jQ5uI4N9ijHWscW
R7WXiY45lju2Ovycya4sV6NjvPOxU+Ta5TrlnuyZ6Jrv5DufOvo7xjt+Ol45hM4u
zmXOJtdvd7rnjaefp9bd6v7m+YLEeBv/QzQU7Ysku785VY4eju/2Jtsda7LV3zLP
y3J/mfoah+gKNRPVu1Qd7dPbB7X3apeo8jUarUPbX99k9Ji2m6+at5huG10m0rzE
Atk220sc61wP3AnIW+QtVoF9wOcTTJIkFuMYcBxIBTvBbBCH+1MqqDeYX5hXGBAn
knuFfYi9jr2HdZLVj/uFv4l7kS3ljRRk+BBckH9bMN9Xyt/IieWlCIP8O31zBW2C
QuFpb8J0+BcIE32O8ob5oMJh/gcCzAEP/f/yn+q/xn+U/zb/Wr8w4WTf2T4mjofZ
jTYWygWmEvlomPupc4ODsI+3l9gd7iIkDWlG6tGLGB2Toq/Q/7ARRDQgg0Lg3eA5
kuH9UuEg0Yf8Ri4hU8jr+BPE7D7lanWMsi+2jbJttu235dpXO9Y7TjhhLw28xGzk
PiiPMprqR+tC+0Jl0MbR8mlraG20gbTdlKOURlomE2RdYCWzYTbJfMLoQXfSWHQD
5S18AtxM/kPOByrAeeA7IBK8CAlgEsqA/wVHgb9BNcAh9xBTyWKyhIwn1uLpxGo8
Hs/HF+AQuRxwgq9hEXULbR+tiLqQ8hpeBYshIfCUvAOYwS6wlPKW2o22gPaDOo86
k/IPVAwdodyiNMJcSgu8lxJPCYYugenQYjANSIJOwjT4AaSBxbT9tDpaBq2Edpdh
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
s8yBDn0AAA==
}

For step 3, I placed the now-familiar "load to-binary decompress" code in front of each embedded sound file data chunk (to decompress the data and load the sound into memory for quick use). I gave each chord it's appropriate chord label (A major, Bb major, C minor, G7, etc.). In doing so, I decided to use all flat symbols for any root notes that had accidentals (i.e., F# = Gb, C# = Db, etc. (no sharps)). Here's how the code for the A major and Bb minor chords looked:

a: load to-binary decompress 64#{
eJxEd2VUW833ddyIE8fdHYoVCi1S2mJ1F+ruTvvU3d3dC1RpC7S4ExwSJFgSSEhC
EmIQIvfl9//yrr32mTMz58vcNXfP2XOTEhIQx0C ...

bbm: load to-binary decompress 64#{
eJwstwVcU9//P757d9eMbQwYMWB0d4cgraCogIgdKHY38lb0rZjYXW8VOxGbDukc
3TVqCWPdv32+///j9Tj3nnvuOa9+Pc85iQtjYkr ...

Here was the full list of chord labels I created (the underscore symbol was a label that I gave to a silent sound that I recorded, to be used for beats of rest). I manually labeled each of the chord data with the following labels (using my text editor's search, copy, and paste facilities, that took about ten minutes):

a bb b c db d eb e f gb g ab 
am bbm bm cm dbm dm ebm em fm gbm gm abm 
a7 bb7 b7 c7 db7 d7 eb7 e7 f7 gb7 g7 ab7 
adim7 bbdim7 bdim7 cdim7 dbdim7 ddim7 
ebdim7 edim7 fdim7 gbdim7 gdim7 abdim7
am7b5 bbm7b5 bm7b5 cm7b5 dbm7b5 dm7b5 
ebm7b5 em7b5 fm7b5 gbm7b5 gm7b5 abm7b5 
am7 bbm7 bm7 cm7 dbm7 dm7 ebm7 em7 fm7 gbm7 gm7 abm7
amaj7 bbmaj7 bmaj7 cmaj7 dbmaj7 dmaj7
ebmaj7 emaj7 fmaj7 gbmaj7 gmaj7 abmaj7
_

Step 4 in the outline just required building the following simple GUI. It consists of a few labels, a text area to hold the user-entered chords, a text field for the tempo, and a couple buttons to stop and start the music action. I also decided to add the buttons from step 6 - I even put all that code in here - all that was required was to save and load the contents of the text area. Simple:

view center-face layout [
    across
    h2 "Chords:"
    tab
    chords: area 392x300 trim {}
    return
    h2 "Delay:"
    tab
    tempo: field 50 "0.35" text "(seconds)"
    tabs 40 tab
    btn "PLAY" []
    btn "STOP" []
    btn "Save" [save to-file request-file/save chords/text]
    btn "Load" [chords/text: load read to-file request-file show chords]
    btn "HELP" [
        alert {}
    ]
]

Now all that's left is step 5. I started by loading the user entered list of chords into a block:

sounds: to-block chords/text

I also gave a label to the tempo, and made sure it was treated as a decimal value:

the-tempo: to-decimal tempo/text

I took the play-sound function that you've seen earlier, and used its code inside a foreach loop that played each of the sounds in the user provided list (now in the "sounds" block). Because those chord labels now refer to actual pieces of sound data that can be inserted and played directly by the sound port, this was simple:

wait 0
sound-port: open sound://
foreach sound sounds [
    do rejoin ["insert sound-port " reduce [sound]]
    wait sound-port
    wait the-tempo 
]

I wrapped the above foreach loop in a forever loop, because I wanted the chord progression to repeat continuosly. To stop the music, I first thought that I'd need some multitasking code, but it turns out that it was simpler than expected. All I did was create a flag variable (the word "play"), which was set to false when the GUI stop button was clicked. Inside the above foreach loop, I checked to see if the play variable had been set to false, and if so, broke out of the loop. The stop button then simply closed the sound port after setting the variable flag to false. Below is the full code for the PLAY and STOP buttons in the GUI. Simple :)

btn "PLAY" [
    play: true
    the-tempo: to-decimal tempo/text
    sounds: to-block chords/text
    wait 0
    sound-port: open sound://
    forever [
        foreach sound sounds [
            if play = false [break]
            do rejoin ["insert sound-port " reduce [sound]]
            wait sound-port
            wait the-tempo 
        ]
        if play = false [break]
    ]
]
btn "STOP" [
    play: false
    close sound-port
]

To finish up the program, I added some instruction text to the alert which pops up when the help button is clicked, and I put in an example chord progression that appears in the text area by default (the chords to "Hotel California"). In testing the program, I realized that if the GUI was closed before the music was stopped, play would continue without any way to stop it. The operating system's task manager was the only way to end the music at that point. To fix that bug, I added some code to trap the close button and stop the music (set the play flag to false and close the sound port), along with a request to "really close the program?". You've seen that sort of code in several previous examples.

Here's the final program (a shortened version, WITHOUT the chord data required to play the example):

REBOL [title: "Chord Accompaniment Player"]

play: false
insert-event-func [
    either event/type = 'close [
        if play = true [play: false close sound-port]
        really: request "Really close the program?"
        if really = true [quit]
    ][
        event
    ]
]

{
    .
    .
    .

    all the chord data goes here

    bm: load to-binary decompress 64#{
    eJw8dgdUU0/w7k0vJNTQe++9SpUqCogIiiKgIIq9YAdU7L397NgVRAQEpEnvvfca
    CCUhQEhCIJUk9/E/7/3f2bN7z92d...
    } 

    .
    .
    .

}

view center-face layout [
    across
    h2 "Chords:"
    tab
    chords: area 392x300 trim {
        bm bm bm bm
        gb7 gb7 gb7 gb7
        a a a a 
        e e e e
        g g g g
        d d d d 
        em em em em
        gb7 gb7 gb7 gb7
        g g g g
        d d d d
        gb7 gb7 gb7 gb7
        bm bm bm bm
        g g g g
        d d d d
        em em em em
        gb7 gb7 gb7 gb7
    }
    return
    h2 "Delay:"
    tab
    tempo: field 50 "0.35" text "(seconds)"
    tabs 40 tab
    btn "PLAY" [
        play: true
        the-tempo: to-decimal tempo/text
        sounds: to-block chords/text
        wait 0
        sound-port: open sound://
        forever [
            foreach sound sounds [
                if play = false [break]
                do rejoin ["insert sound-port " reduce [sound]]
                wait sound-port
                wait the-tempo 
            ]
            if play = false [break]
        ]
    ]
    btn "STOP" [
        play: false
        close sound-port
    ]
    btn "Save" [save to-file request-file/save chords/text]
    btn "Load" [chords/text: load read to-file request-file show chords]
    btn "HELP" [
        alert {
            This program plays chord progressions.  Simply type in
            the names of the chords that you'd like played, with a
            space between each chord.  For silence, use the
            underscore ("_") character.  Set the tempo by entering a 
            delay time (in fractions of second) to be paused between
            each chord.  Click the start button to play from the 
            beginning, and the stop button to end.  Pressing start
            again always begins at the first chord in the 
            progression.  The save and load buttons allow you to 
            store to the hard drive any songs you've created.
            Chord types allowed are major triad (no chord symbol - 
            just a root note), minor triad ("m"), dominant 7th 
            ("7"), major 7th ("maj7"), minor 7th ("m7"), diminished
            7th ("dim7"), and half diminished 7th ("m7b5").
            *** ALL ROOT NOTES ARE LABELED WITH FLATS (NO SHARPS)
            F# = Gb, C# = Db, etc...
        }
    ]
]

A full, playable version, with the complete data set of embedded chords, can be found at http://musiclessonz.com/rebol_tutorial/backup.r.

Here are a few chord examples to load. All the chords:

a bb b c db d eb e f gb g ab 
am bbm bm cm dbm dm ebm em fm gbm gm abm 
a7 bb7 b7 c7 db7 d7 eb7 e7 f7 gb7 g7 ab7 
adim7 bbdim7 bdim7 cdim7 dbdim7 ddim7 
ebdim7 edim7 fdim7 gbdim7 gdim7 abdim7
am7b5 bbm7b5 bm7b5 cm7b5 dbm7b5 dm7b5 
ebm7b5 em7b5 fm7b5 gbm7b5 gm7b5 abm7b5 
am7 bbm7 bm7 cm7 dbm7 dm7 ebm7 em7 fm7 gbm7 gm7 abm7
amaj7 bbmaj7 bmaj7 cmaj7 dbmaj7 dmaj7
ebmaj7 emaj7 fmaj7 gbmaj7 gmaj7 abmaj7
_ _ _ _

Brown Eyed Girl:

g g c c g g d d7
g g c c g g d d7
g g c c g g d d7
g g c c g g d d7
g g c c g g d d7
g g c c g g d d7
c c d d  g g em em c c d d

Practice hard!

31.13 Case 12 - A Simple Image Gallery CGI Program

Here's a simple case study to finish up with. When putting together the web site for my music lesson business, I wanted to regularly add photos of students performing at various events. At first, I just uploaded the photos individually, and added a link to the folder that contained them. As the collection grew, I wanted users to see the images more easily, without having to click on each individual file name. So, I put together a simple flash presentation that showed the images one by one. But updating the presentation required too much maintenance. What I wanted was to simply upload the photos, and have them all display on a single web page, without any required maintenance. This type of small cgi application was perfectly suited to REBOL. It only took a few minutes to write, and it now gets used every day.

For this program, I didn't actually write out an outline. The thought process was simple enough to code directly. Here's the outline and pseudo code I worked through in my head:

  1. Create a simple command line script to read the file names contained in a local folder. Use a foreach loop to run through the list.
  2. Within the foreach loop, check for specified image types (extensions in each file name), and only work with those files. Add a counter to display the total number of images. To do that, use a counter variable and increment it each time through the loop.
  3. Include the above code in a CGI structure that runs on the web site, dynamically builds the html code required to display each image in the list, and prints that code to the browser so the visitor sees each image.

Here's the code for step 1:

REBOL []

folder: read %.
foreach file folder [
    print file
]
halt

For step 2, I added the counter variable, and checked for specified image types using an "if any" conditional expression:

REBOL []

folder: read %.
count: 0
foreach file folder [
    if any [
        find file ".jpg" 
        find file ".gif" 
        find file ".png" 
        find file ".bmp"
    ] [
        print file
        count: count + 1
    ]
]
print rejoin [newline "Total Images: " count]
halt

I shortened that script a bit by using an alternate version which relies on nested foreach loops. The alternate code makes the list of potential image types easier to extend in the future:

REBOL []

folder: read %.
count: 0
foreach file folder [
    foreach ext [".jpg" ".gif" ".png" ".bmp"] [
        if find file ext [
            print file
            count: count + 1
        ]
    ]
]
print rejoin [newline "Total Images: " count]
halt

For the last step, I borrowed a line from the earlier "guitar chord diagram maker" example. It builds the html required to display each image on the page:

print rejoin [{<img src="} file {">}]

Finally, I added the typical CGI headers and page formatting code required to make REBOL CGI scripts perform correctly (see all the previous CGI examples in this tutorial for similar patterns):

#! /home/path/public_html/rebol/REBOL -cs
REBOL [title: "Photo Viewer"]
print "content-type: text/html^/"
print [<HTML><HEAD><TITLE>"Jam Session Photos"</TITLE></HEAD><BODY>]
print read %pageheader.html

folder: read %.
count: 0
foreach file folder [
    foreach ext [".jpg" ".gif" ".png" ".bmp"] [
        if find file ext [
            print [<BR> <CENTER>]
            print rejoin [{<img src="} file {">}]
            print [</CENTER>]
            count: count + 1
        ]
    ]
]
print [<BR>]
print rejoin ["Total Images: " count]
print read %pagefooter.html

I uploaded that script to the folder containing images on our web server, and updated the link to the photos on our web site. Now, we just upload new images directly to the server, and when web site visitors click the "Photos" link on our site, they instantly see a dynamically created web page full of all images currently contained in that folder.

Being able to write short, useful scripts like that is one of the great benefits of learning REBOL. You can create perfectly tailored simple scripts to get things done on your home/business PC, your web site, etc. And with REBOL, it rarely takes more than a single sitting to write, test, and implement such small programs.

32. Other Topics

32.1 6 REBOL Flavors

This tutorial covers a version of the REBOL language interpreter called Rebol/View. Rebol/View is actually only one of several available REBOL releases. Here's a quick description of the different versions:

  1. View - free to download and use, it includes language constructs used to create and manipulate graphic elements. View comes with the built-in dialect called "VID", which is a shorthand mini-language used to display common GUI widgets. View and VID dialect concepts have been integrated throughout this document. The "layout" word in a typical "view layout" GUI design actually signifies the use of VID dialect code in the enclosed block. The VID dialect is used internally by the REBOL interpreter to parse and convert simple VID code to lower level View commands, which are composed from scratch by the rudimentary display engine in REBOL. VID makes GUI creation simple, without the need to deal with graphics at a rudimentary level. But for fine control of all graphic operations, the full View language is exposed in Rebol/View, and can be mixed with VID code. View also has a built-in "draw" dialect that's used to compose and alter images on screen. Aside from graphic effects, View has built in sound, and access to the "call" function for executing command line applications. The newest official releases of View can be download from http://rebol.com/view-platforms.html. The newest test versions are at http://www.rebol.net/builds/. Older versions are at http://rebol.com/platforms-view.html.
  2. Core - a text-only version of the language that provides basic functionality. It's smaller than View (about 1/3 to 1/2 the file size), without the GUI extensions, but still fully network enabled and able to run all non-graphic REBOL code constructs. It's intended for console and server applications, such as CGI scripting, in which the GUI facilities are not needed. Core is also free and can be downloaded from http://rebol.com/platforms.html. Newest versions are at http://www.rebol.net/builds/. Older versions are at http://rebol.com/platforms-core.html.
  3. View/Pro - created for professional developers, it adds encryption features, Dll access and more. Pro licenses are not free. See http://www.rebol.com/purchase.html.
  4. SDK - also intended for professionals, it adds the ability create stand-alone executables from REBOL scripts, as well as Windows registry access and more to View/Pro. SDK licenses are not free.
  5. Command - another commercial solution, it adds native access to common database systems, SSL, FastCGI and other features to View/Pro.
  6. Command/SDK - combines features of SDK and Command.

Some of the functionalities provides by SDK and Command versions of REBOL have been enabled by modules, patches, and applications created by the REBOL user community. For example, mysql and postgre database access, dll access, and stand-alone executable packaging can be managed by free third party creations (search rebol.org for options). Because those solutions don't conform to official REBOL standards, and because no support for them is offered by REBOL Technologies, commercial solutions by RT are recommended for critical work.

32.2 Spread the Word: 7 Reasons to Learn and Use Rebol

REBOL is an extraordinary tool, but it's relatively young and unknown. To become more useful and mature, the REBOL community needs the support of a growing user base. New modules, dialects, and useful functions are created by active coders, and that helps make REBOL even more powerful. If you like the benefits that REBOL offers, take a moment and send this link to a friend. Doing that will help to spread the "Rebolution" :)

  1. You can use it to build an enormous variety of applications with modern graphics, database connectivity, network functionality, simple CGI interface for web programming, and much more. Instead of learning to use a variety of languages and development tools to complete various tasks, you can focus on improving your skills in one simple paradigm. There is no other programming tool that allows you to accomplish so many different types of work, so simply.
  2. REBOL is easy to learn. Even absolute beginners can learn to complete complex and useful programming tasks within a few weeks. A comprehensive variety of built in data types, native network connectivity, super-simple GUI creation, easy data manipulation and parsing features, and a consistent interface to all types of files, data, and functions make it straightforward to use. There is not a simpler language to learn, and Rebol's consistent language style/syntax is maintained throughout the learning curve.
  3. Compared to other development environments, the REBOL interpreter is tiny. In it's heaviest uncompressed form, the REBOL environment is ~600k. You can distribute the entire compressed language, including the interpreter, built-in documentation, and more features than can be found in other multimegabyte development environments, in 100k-400k - depending on the version you choose. No other modern general purpose development environment close in terms of download size.
  4. Code written in REBOL is able to run unchanged on over 40 operating systems. Aside from providing a great development solution for cross platform tasks, you can also use the interpreter as a consistent interface to operating systems with which you're unfamiliar. It's useful as a compact multiplatform file manager, text editor, calculator, email client, ftp client, newsreader, image viewer/editor, etc.
  5. Rebol/View and Rebol/Core are free for commercial and non-commercial use.
  6. The REBOL user community is friendly and knowledgeable. Help for most problems and questions is generally available on the REBOL mailing list, and the response time is usually very quick (REBOL users love to demonstrate just how capable and elegant REBOL is).
  7. The REBOL language has the potential to build dialects that are strikingly human-readable and lightweight. Like any other programming language, it makes use of variables, function definitions, object constructs, etc., but it's got some unusual twists that enable language extensions which are simple for even inexperienced programmers to implement. The built-in dialects in REBOL demonstrate just how accessible Rebol's underlying syntax concepts can make complex tasks, even for untrained coders. Because of that, REBOL offers new potential for average users to program their computers.

33. What Next?

At this point, you've seen a useful set of concepts and techniques, but you've only just begun.

If you take a close look at the code that's been presented in this tutorial, play with it and absorb the full meaning of how it works, you'll understand some of the real power, flexibility, and ease with which REBOL allows you to store, retrieve, display, and otherwise manipulate data in any computer environment. By using the built in function words in REBOL, you can do a lot of useful programming without writing much code, and with a very easy learning curve. You can create GUIs to input, save, transfer, and display data. You can read, send, alter, and display the text of emails and web pages. You can create, view, alter and manipulate images and sounds. You can display, sort, filter, and organize database-like blocks of text and binary data.

Remember that REBOL can run on just about any computer, web server, etc., and can allow you to deal with all types of data just as easily: text, images, sounds, money values, times and dates, web addresses, etc. It has built in functions to easily work with networks. Your REBOL programs can be written in parts that run on separate machines and work in tandem by communicating across the Internet. Once you understand how to save data using word variables and block structures, and perform actions with homemade functions, you're ready to delve more deeply into the syntax and thought processes that make the language flow.

Begin to study and understand the logic and work flow that occurs in longer programs. Figure out what types of data you want your programs to work with. Figure out how to get that data into your programs (input from a GUI, read from a local hard drive or CD, downloaded from a remote web site or email server, output from another program and saved to disk, etc.). Determine data structures which can represent it in your program (simple variables, simple block lists, compound block structures, database systems, etc.). Decide where you want data to be saved (on a local disk, on a file server in your local network, on a web server on the Internet, etc). Decide what actions you want to perform on that data (organizing, sorting, categorizing, editing, erasing, filtering, separating, transferring, displaying, listening to, etc.), and use/create functions that perform those actions. In REBOL, all those things are easily understood and accomplished. Familiarize yourself with common approaches to managing typical situations in the types of programs you want to write. Become familiar with the data structures, functions, conditional operations, loops, and general code flow seen in those types of programs. Read and memorize code for programs that others have written. Explore and memorize all the built-in words in the language. Learn how others create and use new words to solve common programming problems.

By doing those things, you'll learn to speak the REBOL language fluently. The tutorial at http://www.rebol.com/docs/rebol-tutorial-3109.pdf provides a nice summary of fundamental concepts. It's a great document to read next. To learn REBOL in earnest, start by reading the REBOL core users manual: http://rebol.com/docs/core23/rebolcore.html. It covers all of the data types, built-in word functions and ways of dealing with data that make up the Rebol/Core language (but not the graphic extensions in View). It also includes many basic examples of code that you can use in your programs to complete common programmatic tasks. Also, be sure to keep the REBOL function dictionary handy whenever you write any REBOL code: http://rebol.com/docs/dictionary.html. It defines all the words in the REBOL language and their specific syntax use. It's also helpful in cross-referencing function words that do related actions in the language. Along the way, read the REBOL View and VID documents at: http://rebol.com/docs/easy-vid.html , http://rebol.com/docs/view-guide.html , and http://rebol.com/docs/view-system.html. Those documents explain how to write Graphical User Interfaces in REBOL. Once you've got an understanding of the grammar and vocabulary of the language, dive into the REBOL cookbook: http://www.rebol.net/cookbook/. It contains many simple and useful examples of code needed to create real-world applications. When you've read all that, finish the rest of the documents at http://rebol.com/docs.html to get more specific examples about how to program using sound, network, web sites, compression, security, and other features in common use.

Beyond the basic documentation, there is a library of hundreds of commented and approved source codes for REBOL programs available at http://rebol.org. It's a great way to immerse yourself into reading the language as it exists in the real world. In addition, you can use it to copy and paste examples of code from other programs as a basis for programs you want to write (be sure to give credit to the author(s) whenever you do anything of that sort). There's also a searchable mailing list archive with nearly 45,000 emails at the same rebol.org web site. It contains answers to numerous questions encountered by REBOL programmers. There are many other web sites such as http://www.codeconscious.com/rebol/, http://www.rebolforces.com/, and http://www.fm.vslib.cz/~ladislav/rebol/ that provide more help in understanding and using the language. For a complete list of all web pages and articles related to REBOL, see http://dmoz.org/Computers/Programming/Languages/REBOL/

Finally, the REBOL interpreter itself is a fantastic source of code and reference material. The built-in word "help" provides required syntax structure for all of the built-in words in REBOL, and the word "what" lists all built-in words. Together, those two words provide a reference that's sufficient for most situations in which you need syntax help - without any external guide. You can also use the built-in word "source" to get the actual internal REBOL code that defines many of the mid-high level function words in the language. The interpreter also provides an embedded way for the community to share useful code. The REBOL "desktop" that appears by default when you run the view.exe interpreter is a gateway into a world of "Rebsites" that developers use to post code for programs they've created. Surfing the public rebsites on which developers make shared code available is a great way to explore the language more deeply. All of the code in the rebol.org archive, and much more, is available on the rebsites (It's amazing how much that little view.exe file lets you do!)

If you're interested at all in programming, you'll find all of the REBOL documentation and resources interesting, and a little addictive, because they show you how to do virtually everything you've ever imagined doing as a programmer. REBOL is a tiny tool (compared to the file size of other interpreters and compilers), it's very powerful, and it's completely cross-platform capable. You'll be able to program local Windows computers, as well as Linux web servers, Macs, and other types of computers used in all sorts of different environments. You'll be able to write business applications, audio-visual applications, network and Internet applications, games, etc. And you'll get started right away, with a quick learning curve that'll have you doing useful things immediately as a programmer. On top of that, you'll learn fundamentals about programming that you'll carry around to other languages as well.

33.1 A Final Point

REBOL is a language that neatly "wraps" up most common functions that various operating systems enable. It provides a single, simple format that lets you talk to all different computers in the same way. It's got it's own way of speaking that is different from many other languages (see http://www.ddj.com/documents/s=3211/nam1012432597/index.html to get an idea of how REBOL is different). In technical terms, that grammar and syntax is called the "api". If you continue to pursue programming in other languages, and in various environments, you'll encounter different language apis. Eventually, as a serious programmer, you'll typically learn to deal with the raw api of the operating system you're working on. It is the base language that most other languages are actually translating to. Because the operating system needs to access the computer hardware quickly, it is written in a "lower level" language - one that is formatted to think more like the computer's raw calculations, and less like human speech.

With REBOL, you can do most typical things that programmers want to do, but there are many functions in the various operating system api's that aren't included (i.e., web cam access, sound input, low level hardware control, etc.). To do that, be prepared to explore the raw operating system api, and the language(s) in which it was written. On Windows, Unix, Macintosh, and other platforms, that typically means learning the syntax and structure of the "C" and "C++" languages. Also, learning common methods for accessing shared code files such as .dll's is very important. Once you've learned the full REBOL api, that's a good direction to take in your studies...

Enjoy!

34. Feedback

For feedback, bugs reports, suggestions, etc., please use the form below:

Your Name:


Your Email Address:


Message:


Or contact me by email at:

reverse {moc tod znosselcisum ta lober}

Keywords:

software development, learn to program a computer, how to write software, learn computer programming, easy coding, how to create programs, learn to write code, computer programming tutorial, programming course, learn about programming, coding, easiest way to program, simple programming, best programming language, best computer language, easiest programming language, easiest way to program, learn programming, get started programming

Copyright © Nick Antonaccio 2005-2009, All Rights Reserved

MakeDoc2 by REBOL - 1-Mar-2009