Task 12  Task 12: String Manipulation  Task 12


Not all games involve pressing buttons to shoot enemies on the screen. Many of the all time favorites involve entering text to play such as Hangman, Scrabble, crossword puzzles, Boggle and Text Twist just to name few. Luckily QB64 has a rich feature set of commands that allow the programmer to manipulate text entry in many different ways.

- UCASE$() and LCASE$() -

When a user answers a program's question, for instance, "What is the capital of Ohio?", how will the user answer? Will the user type in the correct answer? Will the answer be in UPPER or lower case or a cOmbInaTion of both? How do you, as the programmer, check for all of these variations? The answer lies in turning the user's answer into something that can easily be checked. Type the following program in and execute it.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM Answer$

'----------------------------
'- Main Program Begins Here -
'----------------------------

PRINT
INPUT "What is the capital of Ohio? > ", Answer$
PRINT
IF UCASE$(Answer$) = "COLUMBUS" THEN
    PRINT "Correct!"
ELSE
    PRINT "You lose!"
END IF

It doesn't matter how the user types in the correct answer of Columbus using any combination of upper and lower case characters. As long as Columbus was typed in the program can identify it because of the UCASE$ function. UCASE$ returns a string value equal to the string passed to it in all UPPER case.  Likewise, LCASE$ does just the opposite, returning a string value equal to the string passed to it in all lower case. The program above would function just as well with:

IF LCASE$(Answer$) = "columbus" THEN

- LTRIM$() and RTRIM$() -

But, what if the user accidently pressed the space key before typing in the correct answer? The example program would see this as the wrong answer. Modify this line of code:

IF UCASE$(Answer$) = "COLUMBUS" THEN

to read

IF LTRIM$(UCASE$(Answer$)) = "COLUMBUS" THEN


then run the program again, this time answering the question with one or more spaces at the beginning. You'll see that it still works! This is because LTRIM$ returns a string value equal to the string passed in with all leading spaces removed (spaces to the left). Likewise, RTRIM$ does something similar, returning a string value equal to the string passed to it with all trailing spaces removed (spaces to the right). So, in order to ensure that all spaces are removed before and after the user's answer, the following line would be used:

IF LTRIM$(RTRIM$(UCASE$(Answer$))) = "COLUMBUS" THEN

Yes, the above line looks complicated, but just use the order of operations to sort it out. Let's imagine for a moment the user types in _ColUmbus__ (underscores are being used to show spaces) as the answer to the question. The first function to activate in the line above would be UCASE$ because the order of operations dictates the inner most set of parenthesis gets precedence.

UCASE$("_ColUmbus__")

would return "_COLUMBUS__". Then

RTRIM$("_COLUMBUS__")

would return "_COLUMBUS". And finally

LTRIM$("_COLUMBUS")

would return "COLUMBUS" the string we are looking for.


You could even modify Answer$ directly to hold the result after string manipulation like so:

Answer$ = LTRIM$(RTRIM$(UCASE$(Answer$)))

The above line takes what is currently stored in the variable Answer$, modifies it through string manipulation, and then stores the result back into Answer$.


- INSTR() -

The previous examples work great for single word answers, but what if the user were to answer, "I think it's Columbus?" The INSTR function can look into a base string for a search string and return a number corresponding to where it found the search string within the base string. Let's modify our program again to see this is action. When you execute the program type in your answer as "It is columbus!" and see if you are correct.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM Answer$

'----------------------------
'- Main Program Begins Here -
'----------------------------

PRINT
INPUT "What is the capital of Ohio? > ", Answer$
PRINT
IF INSTR(UCASE$(Answer$), "COLUMBUS") THEN
    PRINT "Correct!"
ELSE
    PRINT "You lose!"
END IF

INSTR requires a string to be searched, called the base string, and then another string to look for, called the search string. In the above example INSTR is looking into the capitalized user's answer for the search string "COLUMBUS". If a number other than 0 (zero) is returned by INSTR the IF...THEN statement becomes true indicating the search string was found.

INSTR also has another trick up its sleeve; it can find multiple occurances of the search string in the base string and report back where it finds all of them. Type in the following program to see this in action.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM Position%
DIM Phrase$
DIM Search$
DIM NextLine%

'----------------------------
'- Main Program Begins Here -
'----------------------------

Phrase$ = "The rain in spain falls mainly on the plain."
Search$ = "ain"
Position% = 0
NextLine% = 4
PRINT Phrase$
DO
    Position% = INSTR(Position% + 1, Phrase$, Search$)
    IF Position% <> 0 THEN
        LOCATE NextLine%, 1
        NextLine% = NextLine% + 1
        PRINT "Found "; CHR$(34); Search$; CHR$(34); " at position"; Position%
        LOCATE 2, Position%
        PRINT CHR$(24); ' up arrow symbol
    END IF
LOOP UNTIL Position% = 0

INSTR can accept an optional position number to start at within the base string. Once an instance of "ain" is found, that position is reported and then the base string is searched again starting at the last position + 1. This continues until no more instances of the search string are present, at which time Position% becomes 0 (zero).

- STR$() and VAL() -

Strings can't be true numeric values and numeric values can't be true strings, that is of course unless you convert them. The STR$ function is used to convert a numeric value to a string and the VAL function is used to convert a string to a numeric value. (Yes, we discussed VAL before but a review never hurts.)

LINE INPUT "Enter a number between 1 and 10 > ", Number$
Value! = VAL(Number$)

In the above example a number is being asked for and saved into a string because of the LINE INPUT statement. The following line however converts the string value of Number$ to a true numeric value and saves the result in the variable Value!. If the characters held in a string are non-numeric however, such as "Hello World", VAL will return the value of 0 (zero).

INPUT "Enter a number between 1 and 10 > ", Value%
Number$ = STR$(Value%)

The above example does the opposite by converting the numeric value in Value% to a string and storing the result in Number$. Positive numeric values converted to strings will always containing a leading space. The space is present for the possibly of a minus sign. For example:

PRINT "*"; STR$(10); "*" ' * 10* printed to the screen
PRINT "*"; STR$(-10);"*" ' *-10* printed to the screen

If you want a positive number to contain no leading space you'll need to use the LTRIM$ function along with it like so:

Number$ = LTRIM$(STR$(Value%))


- LEN() -

The LEN function returns the number of all characters contained in a string, or its length.

Phrase$ = "The rain in Spain falls mainly on the plain."
PRINT LEN(Phrase$) ' 44 is printed to the screen


- LEFT$(), RIGHT$() and MID$() -

There are going to be times you need to parse, or analyze and break apart, a string into smaller pieces. A perfect example of this is when you use the TIME$ and DATE$ functions to retrieve the time and date from QB64. TIME$ reports back the time in string form as HH:MM:SS and DATE$ reports back the date as MM-DD-YYYY. In order to get the individual hours, minutes and seconds from the TIME$ string, and the individual month, day and year from the DATE$ string you'll need LEFT$, RIGHT$ and MID$. The following example program shows how this can be done.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM MonthName$(12) ' array storing the names of the months
DIM Hours% '         numeric value of hour
DIM Month% '         numeric value of month
DIM Day% '           numeric value of day
DIM Year% '          numeric value of year
DIM Suffix$ '        day suffix
DIM AmPm$ '          AM or PM

'----------------------------
'- Main Program Begins Here -
'----------------------------

_TITLE "LEFT$, RIGHT$ and MID$ Demo"
MonthName$(1) = "January" '                                                store the month names
MonthName$(2) = "February"
MonthName$(3) = "March"
MonthName$(4) = "April"
MonthName$(5) = "May"
MonthName$(6) = "June"
MonthName$(7) = "July"
MonthName$(8) = "August"
MonthName$(9) = "September"
MonthName$(10) = "October"
MonthName$(11) = "November"
MonthName$(12) = "December"
DO '                                                                       begin main loop
    Month% = VAL(LEFT$(DATE$, 2)) '                                        extract value of month
    Day% = VAL(MID$(DATE$, 4, 2)) '                                        extract value of day
    Year% = VAL(RIGHT$(DATE$, 4)) '                                        extract value of year
    Hours% = VAL(LEFT$(TIME$, 2)) '                                        extract value of hours
    IF Hours% > 12 THEN '                                                  military time?
        Hours% = Hours% - 12 '                                             yes, convert to civilian
        AmPm$ = "PM" '                                                     it's the afternoon
    ELSE '                                                                 no
        AmPm$ = "AM" '                                                     it's the morning
    END IF
    IF Day% = 1 OR Day% = 21 OR Day% = 31 THEN '                           one of these days?
        Suffix$ = "st," '                                                  yes, day ends in st
    ELSEIF Day% = 2 OR Day% = 22 THEN '                                    no, one of these days?
        Suffix$ = "nd," '                                                  yes, day ends in nd
    ELSEIF Day% = 3 OR Day% = 23 THEN '                                    no, one of these days?
        Suffix$ = "rd," '                                                  yes, days ends in rd
    ELSE '                                                                 no
        Suffix$ = "th," '                                                  day must end in th then
    END IF
    LOCATE 2, 2 '                                                          position cursor
    Dt$ = "The current date is " + MonthName$(Month%) '                    build new date string
    Dt$ = Dt$ + STR$(Day%) + Suffix$ + STR$(Year%) + "  "
    PRINT Dt$ '                                                            display date string
    LOCATE 4, 2 '                                                          position cursor
    Tm$ = "The current time is " + RIGHT$("0" + LTRIM$(STR$(Hours%)), 2) ' build new time string
    Tm$ = Tm$ + " Hours, " + MID$(TIME$, 4, 2) + " Minutes and "
    Tm$ = Tm$ + RIGHT$(TIME$, 2) + " Seconds " + AmPm$
    PRINT Tm$ '                                                            display time string
LOOP UNTIL INKEY$ <> "" '                                                  end loop if key pressed
SYSTEM '                                                                   return to Windows

Simple
Figure 1 - Parsing down DATE$ and TIME$

In the example above LEFT$ was used to pull the month portion of DATE$. LEFT$ will return the number of characters specified starting at the beginning (the left) of a string. This line:

Month% = VAL(LEFT$(DATE$, 2)) ' parse MM from MM-DD-YYYY

gets the first two characters from DATE$, converts those characters to a true numeric value using VAL and then saves the result in Month%.

LEFT$ was also used in the same manner to get the hour potion of TIME$.

Hours% = VAL(LEFT$(TIME$, 2)) ' parse HH from HH:MM:SS

RIGHT$ is used to return the number of characters specified starting at the end (the right) of a string. RIGHT$ was used to get the year portion of DATE$ like so:

Year% = VAL(RIGHT$(DATE$, 4)) ' parse YYYY from MM-DD-YYYY

However, the day portion of DATE$ falls in the middle of the string so MID$ needs to be used to get that value. MID$ requires two numbers to function, a starting position within the string and then the number of chacarters to grab starting from that position. Here, MID$ was used to get the day portion of DATE$.

Day% = VAL(MID$(DATE$, 4, 2)) ' parse DD from MM-DD-YYYY

MID$ has been instructed to start at the fourth character in DATE$ and then grab that character plus the next one, for a total of 2 characters.

The example program had no need to get the numeric values of TIME$'s minutes and seconds, so MID$ and RIGHT$ were used again when the minutes and seconds portion of TIME$ were needed to display on the screen.


Tm$ = "The current time is " + RIGHT$("0" + LTRIM$(STR$(Hours%)), 2) ' build new time string
Tm$ = Tm$ + " Hours, " + MID$(TIME$, 4, 2) + " Minutes and "
Tm$ = Tm$ + RIGHT$(TIME$, 2) + " Seconds " + AmPm$


String manipulation using LEFT$, RIGHT$ and MID$ are very common statements that programmers use in many programs that involve strings. Here is another example program using the previous program's string manipulation techniques to build an LED scrolling date and time clock.

'-------------------------------------------------------
'Description: Displays an LED scrolling sign calendar
'Author     : Terry Ritchie
'Date       : 11/13/13
'Version    : 1.0
'Rights     : Open source, free to modify and distribute
'Terms      : Original author's name must remain intact
'-------------------------------------------------------

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

CONST ROUND = 0 '    use to make round LEDs
CONST SQUARE = 1 '   use to make square LEDs

TYPE LED
    Screen AS LONG ' the LED screen to show on screen
    Image AS LONG '  the LED image to work on in the background
    Mask AS LONG '   the LED mask to place over the LED image
END TYPE

DIM LED AS LED '     structure variable to hold images
DIM Days$(6) '       days of the week names
DIM Months$(12) '    months of the year names
DIM Message$ '       scrolling message
DIM Mpos% '          current position of scrolling message
DIM Tm$ '            current time
DIM Dt$ '            current date
DIM Hours% '         hour value extracted
DIM Minutes% '       minute value extracted
DIM Seconds% '       second value extracted
DIM Month% '         month value extracted
DIM Day% '           day value extracted
DIM Year% '          year value extracted
DIM Suffix$ '        day suffix
DIM AmPm$ '          AM or PM
DIM Salute$ '        message salutation

'----------------------------
'- Main Program Begins Here -
'----------------------------

Days$(0) = "Sunday, " '                                                    week day names
Days$(1) = "Monday, "
Days$(2) = "Tuesday, "
Days$(3) = "Wednesday, "
Days$(4) = "Thursday, "
Days$(5) = "Friday, "
Days$(6) = "Saturday, " '                                                  month names
Months$(1) = "January"
Months$(2) = "February"
Months$(3) = "March"
Months$(4) = "April"
Months$(5) = "May"
Months$(6) = "June"
Months$(7) = "July"
Months$(8) = "August"
Months$(9) = "September"
Months$(10) = "October"
Months$(11) = "November"
Months$(12) = "December"
LEDSCREEN 128, 16, 10, SQUARE '                                            create LED screen
_TITLE "And the time is ..." '                                             give window a title
Mpos% = 0 '                                                                reset message position pointer
DO '                                                                       begin main loop
    Tm$ = TIME$ '                                                          get current time
    Dt$ = DATE$ '                                                          get current date
    Hours% = VAL(LEFT$(Tm$, 2)) '                                          get value of current hour
    IF Hours% > 12 THEN '                                                  in military time?
        Hours% = Hours% - 12 '                                             yes, convert to civilian time
        AmPm$ = " PM " '                                                   it's the afternoon
    ELSE '                                                                 no
        AmPm$ = " AM " '                                                   it's the morning
    END IF
    Minutes% = VAL(MID$(Tm$, 4, 2)) '                                      get value of current minute
    Seconds% = VAL(RIGHT$(Tm$, 2)) '                                       get value of current second
    Month% = VAL(LEFT$(Dt$, 2)) '                                          get value of current month
    Day% = VAL(MID$(Dt$, 4, 2)) '                                          get value of current day
    Year% = VAL(RIGHT$(Dt$, 4)) '                                          get value of current year
    SELECT CASE Day% '                                                     which day is it?
        CASE 1, 21, 31 '                                                   1, 21 or 31?
            Suffix$ = "st," '                                              yes, date ends in "st"
        CASE 2, 22 '                                                       2 or 22?
            Suffix$ = "nd," '                                              yes, date ends in "nd"
        CASE 3, 23 '                                                       3 or 23?
            Suffix$ = "rd," '                                              yes, date ends in "rd"
        CASE ELSE '                                                        no, some other date
            Suffix$ = "th," '                                              must end in "th" then
    END SELECT
    SELECT CASE Month% '                                                   which month is it?
        CASE 1 '                                                           create holiday/special day salute
            IF Day% = 1 THEN Salute$ = "Happy New Year! " '                based on month and day
            IF Day% = 2 THEN Salute$ = "Martin Luther King Day. "
        CASE 2
            IF Day% = 2 THEN Salute$ = "Groundhog Day. "
            IF Day% = 14 THEN Salute$ = "Happy Valentine's Day! "
            IF Day% = 18 THEN Salute$ = "President's Day (George Washington's Birthday). "
        CASE 3
            IF Day% = 10 THEN Salute$ = "Daylight Savings Time Begins. "
            IF Day% = 17 THEN Salute$ = "Happy Saint Patrick's Day! "
            IF Day% = 20 THEN Salute$ = "Today is the Spring Equinox. "
        CASE 4
            IF Day% = 15 THEN Salute$ = "Did you get your taxes filed? "
        CASE 5
            IF Day% = 12 THEN Salute$ = "Happy Mother's day! "
        CASE 6
            IF Day% = 6 THEN Salute$ = "Remember D-Day. "
            IF Day% = 14 THEN Salute$ = "Flag Day. "
            IF Day% = 16 THEN Salute$ = "Happy Father's day! "
            IF Day% = 21 THEN Salute$ = "The longest day of the year. "
        CASE 7
            IF Day% = 4 THEN Salute$ = "Happy Independance Day! "
        CASE 8
            IF Day% = 19 THEN Salute$ = "National Aviation Day. "
        CASE 9
            IF Day% = 2 THEN Salute$ = "Labor Day. "
            IF Day% = 8 THEN Salute$ = "Happy Grandparent's Day! "
            IF Day% = 22 THEN Salute$ = "Today is the Fall equinox. "
        CASE 10
            IF Day% = 14 THEN Salute$ = "Columbus Day. "
            IF Day% = 31 THEN Salute$ = "Happy Halloween! "
        CASE 11
            IF Day% = 3 THEN Salute$ = "Daylight Savings Time Ends. "
            IF Day% = 11 THEN Salute$ = "Veteran's Day. "
            IF Day% = 28 THEN Salute$ = "Happy Thanksgiving! "
        CASE 12
            IF Day% = 7 THEN Salute$ = "Remember Pearl Harbor. "
            IF Day% = 25 THEN Salute$ = "Merry Christmas! "
            IF Day% = 31 THEN Salute$ = "The Programmer's Birthday! "
        CASE ELSE
            Salute$ = ""
    END SELECT
    Message$ = "                " + Days$(WeekDay%(Month%, Day%, Year%)) ' add day name to message
    Message$ = Message$ + Months$(Month%) + STR$(Day%) + Suffix$ '         add month and day to message
    Message$ = Message$ + STR$(Year%) + "                " '               add year to message
    Message$ = Message$ + RIGHT$("0" + LTRIM$(STR$(Hours%)), 2) '          add hour to message
    Message$ = Message$ + RIGHT$(Tm$, 6) + AmPm$ '                         add minutes, seconds and AM/PM
    IF Salute$ <> "" THEN '                                                is this a special day?
        Message$ = Message$ + "                " + Salute$ '               yes, add special day to message
    END IF
    _DEST LED.Image '                                                      set small image as destination
    COLOR _RGB32(255, 255, 0) '                                            set text color to yellow
    Mpos% = Mpos% + 1 '                                                    increment message pointer
    IF Mpos% > LEN(Message$) THEN Mpos% = 1 '                              reset pointer at end of message
    LOCATE 1, 1 '                                                          position cursor
    PRINT MID$(Message$, Mpos%, 16); '                                     display portion of message
    _DEST 0 '                                                              set LED screen back to destination
    _PUTIMAGE , LED.Image '                                                stretch the small image across screen
    _PUTIMAGE , LED.Mask '                                                 place LED mask over image
    _LIMIT 4 '                                                             4 frames per second
    _DISPLAY '                                                             viola', the screen looks pixelized
LOOP UNTIL INKEY$ <> "" '                                                  end loop when key pressed
SYSTEM '                                                                   return to Windows

'-----------------------------------
'- Subroutine and Function Section -
'-----------------------------------

'------------------------------------------------------------------------------------------------------------

SUB LEDSCREEN (w%, h%, p%, s%)

'******************************************************************************
'**                                                                           *
'** Creates an LED screen                                                     *
'**                                                                           *
'** w% - number of horizontal LEDs                                            *
'** h% - number of vertical LEDs                                              *
'** p% - size in pizels of each LED                                           *
'** s% - shape of pixel (0 = round, 1 = square)                               *
'**                                                                           *
'** Resulting size of LED screen is computed by multiplying number of v/h     *
'** LEDs by LED pixel size.                                                   *
'**                                                                           *
'******************************************************************************

SHARED LED AS LED '  need access to LED screen properties

DIM LEDPixel& '      temporary image to hold single LED pixel
DIM TempScreen& '    temporary screen when switching between LED screens

IF LED.Screen THEN '                                               does an LED screen already exist?
    TempScreen& = _NEWIMAGE(1, 1, 32) '                            yes, create a temporary screen
    SCREEN TempScreen& '                                           switch to the temporary screen
    _FREEIMAGE LED.Screen '                                        remove LED screen image from memory
    _FREEIMAGE LED.Image '                                         remove LED working image from memory
    _FREEIMAGE LED.Mask '                                          remove LED mask image from memory
END IF
LED.Screen = _NEWIMAGE(w% * p%, h% * p%, 32) '                     create LED screen image holder
SCREEN LED.Screen '                                                switch to the LED screen
_SCREENMOVE _MIDDLE '                                              center screen on desktop
IF TempScreen& THEN _FREEIMAGE TempScreen& '                       remove the temporary screen if it exists
LED.Image = _NEWIMAGE(w%, h%, 32) '                                create LED work image
LED.Mask = _COPYIMAGE(LED.Screen) '                                create LED matrix image mask
LEDPixel& = _NEWIMAGE(p%, p%, 32) '                                create LED pixel
_DEST LEDPixel& '                                                  set LED pixel as destination image
CLS '                                                              remove 0,0,0 alpha transparency
LINE (0, 0)-(p% - 1, p% - 1), _RGB32(10, 10, 10), BF '             set background color
SELECT CASE s% '                                                   which pixel shape should be created?
    CASE ROUND '                                                   round pixels
        CIRCLE (p% \ 2, p% \ 2), p% \ 2 - 2, _RGB32(0, 0, 1) '     create round pixel in center of image
        PAINT (p% \ 2, p% \ 2), _RGB32(0, 0, 1), _RGB32(0, 0, 1) ' fill the pixel in
    CASE SQUARE '                                                  square pixels
        LINE (2, 2)-(p% - 2, p% - 2), _RGB32(0, 0, 1), BF '        create square pixel in center of image
END SELECT
_DEST LED.Mask '                                                   set LED mask as destination image
CLS '                                                              remove 0,0,0 alpha transparency
FOR x% = 0 TO w% * p% - 1 STEP p% '                                cycle through horizontal pixel positions
    FOR y% = 0 TO h% * p% - 1 STEP p% '                            cycle through vertical pixel positions
        _PUTIMAGE (x%, y%), LEDPixel& '                            place a pixel image
    NEXT y%
NEXT x%
_FREEIMAGE LEDPixel& '                                             removel pixel image from memory
FOR x% = 0 TO w% * p% - 1 STEP p% * 8 '                            cycle every 8 horizontal LEDs
    LINE (x%, 0)-(x%, h% * p% - 1), _RGB32(0, 0, 0) '              draw a divider line
NEXT x%
FOR y% = 0 TO h% * p% - 1 STEP p% * 8 '                            cycle every 8 vertical LEDs
    LINE (0, y%)-(w% * p% - 1, y%), _RGB32(0, 0, 0) '              draw a divider line
NEXT y%
_SETALPHA 0, _RGB32(0, 0, 1) '                                     set transparency color of mask

END SUB

'------------------------------------------------------------------------------------------------------------

FUNCTION WeekDay% (m%, d%, y%)

'**********************************************************************
'**                                                                   *
'** Calculates the week day and returns the result as an integer from *
'** 0 (Sunday) to 6 (Saturday).                                       *
'**                                                                   *
'**********************************************************************

DIM c% '      current century
DIM s1% '     century leap
DIM s2% '     leap year
DIM s3% '     days in current month
DIM WkDay% '  current week day number
DIM Month% '  current month
DIM Day% '    current day
DIM Year% '   current year

Month% = m% '                             get month value passed in
Day% = d% '                               get day value passed in
Year% = y% '                              get year value passed in
IF Month% < 3 THEN '                      is month Jan or Feb?
    Month% = Month% + 12 '                yes, add 12 to Jan-Feb month
    Year% = Year% - 1 '                   subtract 1 year
END IF
c% = Year% \ 100 '                        split century number
Year% = Year% MOD 100 '                   split year number
s1% = (c% \ 4) - (2 * c%) - 1 '           calculate century leap
s2% = (5 * Year%) \ 4 '                   calculate 4 year leap
s3% = 26 * (Month% + 1) \ 10 '            days in months
WkDay% = (s1% + s2% + s3% + Day%) MOD 7 ' mod weekday totals (0 to 6)
IF WkDay% < 0 THEN WkDay% = WkDay% + 7 '  adjust if necessary
WeekDay% = WkDay% '                       return result

END FUNCTION

'------------------------------------------------------------------------------------------------------------

cool
Figure 2 - Using string parsing to create an even better clock demo

The LED scrolling clock sample above uses most of the commands we have learned so far (and there are a few new ones in there too which we'll eventually get to). This piece of code is rather long but as I've stated before, don't let the length of source code scare you. Take each line one at a time and you'll see by doing this the program doesn't seem so difficult.

- ASC() and CHR$() -

CHR$ was discussed in a previous task, but as a refresher CHR$ returns the ASCII character of the number requested. For example, if you want to print characters that are not accessible by the keyboard, such as the playing card suits, you can use CHR$ to get these for you:

PRINT CHR$(3) ' ♥ heart symbol
PRINT CHR$(4) ' ♦ diamond symbol
PRINT CHR$(5) ' ♣ club symbol
PRINT CHR$(6) ' ♠ spade symbol


The ASC function does just the opposite, returning the ASCII numeric value of a character passed to it.

PRINT ASC("A") ' 65 printed to screen
PRINT ASC(" ") ' 32 printed to screen (a space)

This little example program shows how ASC and CHR$ can be used together to identify keystrokes.


'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM KeyPress$ ' single key presses by user
DIM KeyValue% ' ASCII value of key pressed

'----------------------------
'- Main Program Begins Here -
'----------------------------

DO '                                                  begin main loop
    DO '                                              begin key input loop
        KeyPress$ = INKEY$ '                          get any key pressed
        _LIMIT 30 '                                   check 30 times per second
    LOOP UNTIL KeyPress$ <> "" '                      leave loop if key pressed
    KeyValue% = ASC(KeyPress$) '                      get ASCII value of key pressed
    IF KeyValue% < 32 THEN '                          is value less than 32?
        PRINT " Control key   "; '                    yes, this is a control character
    ELSEIF KeyValue% > 47 AND KeyValue% < 58 THEN '   no, is value between 47 and 58?
        PRINT " Numeric key   "; '                    yes, this is a numeric character
    ELSEIF KeyValue% > 64 AND KeyValue% < 91 THEN '   no, is value between 64 and 91?
        PRINT " UPPERcase key "; '                    yes, this is an upper case character
    ELSEIF KeyValue% > 96 AND KeyValue% < 123 THEN '  no, is value between 96 and 123?
        PRINT " Lowercase Key "; '                    yes, this is a lower case character
    ELSEIF KeyValue% = 32 THEN '                      no, is value 32?
        PRINT " Spacebar Key  "; '                    yes, this is a space character
    ELSE '                                            no
        PRINT " Symbol Key    "; '                    assume all others are symbol characters
    END IF
    PRINT CHR$(26); " "; CHR$(KeyValue%) '            print right arrow and character
LOOP UNTIL KeyPress$ = CHR$(27) '                     leave main loop when ESC key pressed

- STRING$() and SPACE$() -

When you need a lot of the same character in a row STRING$ can deliver. Supply STRING$ with a number and a character and that many characters will be returned.

PRINT STRING$(80, "*") ' 80 asterisks will be printed to screen

You can also supply STRING$ with the ASCII value of a character to achieve the same result.

PRINT STRING$(80, 42) ' 80 asterisks will be printed to the screen

This command comes in especially handy when building text screen boxes by using the extended ASCII characters provided to do so.


'----------------------------
'- Main Program Begins Here -
'----------------------------

PRINT
PRINT " An ASCII box drawing demo"
PRINT " -------------------------"
PRINT
PRINT CHR$(218); STRING$(17, 196); CHR$(191)
PRINT CHR$(179); " Single Line Box "; CHR$(179)
PRINT CHR$(192); STRING$(17, 196); CHR$(217)
PRINT
PRINT CHR$(201); STRING$(17, 205); CHR$(187)
PRINT CHR$(186); " Double Line Box "; CHR$(186)
PRINT CHR$(200); STRING$(17, 205); CHR$(188)

80's
Figure 3 - Text screen graphics - life was good in the 80's

SPACE$ however can only return spaces of the requested length.

PRINT SPACE$(80) ' 80 spaces printed to the screen


- SWAP -

The SWAP statement is used to switch values between two numeric or string variables. The variables having values swapped between them must be of the same type, for instance two integers or two strings. The following example program shows the SWAP statement in action.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM Var1%
DIM Var2%
DIM S1$
DIM S2$

'----------------------------
'- Main Program Begins Here -
'----------------------------

S1$ = "String 1"
S2$ = "String 2"
Var1% = 1
Var2% = 2
PRINT
PRINT " Before swap"
PRINT " -----------"
PRINT
PRINT " Var1% ="; Var1%
PRINT " Var2% ="; Var2%
PRINT " S1$   = "; S1$
PRINT " S2$   = "; S2$
SWAP S1$, S2$
SWAP Var1%, Var2%
PRINT
PRINT " After swap"
PRINT " ----------"
PRINT
PRINT " Var1% ="; Var1%
PRINT " Var2% ="; Var2%
PRINT " S1$   = "; S1$
PRINT " S2$   = "; S2$

Here is one last example program showing some of the string manipulation statements in use to create a hidden password function.

'--------------------------------          ********************************************************
'- Variable Declaration Section -          * Simple hidden password demo highlighting some of the *
'--------------------------------          * string manipulation commands available in QB64.      *
'                                          *                                                      *
DIM Login$ '    login name user supplies   * LEN() - returns the length of a string               *
DIM Password$ ' password user supplies     * ASC() - returns the ASCII value of string character  *
'                                          * LEFT$() - returns left # of characters of a string   *
'----------------------------              * STRING$() - returns a string of same characters      *
'- Main Program Begins Here -              ********************************************************
'----------------------------

PRINT
PRINT " ------------------------"
PRINT " - Ritchie's Web Server -"
PRINT " ------------------------"
PRINT
PRINT " Welcome to my web server!"
PRINT
PRINT " Before you can begin, you must create an account."
DO '                                                                       begin login loop
    PRINT '                                                                blank line
    PRINT " Create a login name between 6 and 16 characters in length ." ' prompt user
    LINE INPUT " Login    > ", Login$ '                                    get login name
LOOP UNTIL LEN(Login$) > 5 AND LEN(Login$) < 17 '                          continue if length ok
DO '                                                                       begin password loop
    Password$ = "" '                                                       clear current password
    DO '                                                                   begin pwd length loop
        PRINT " Enter a password that is at least 8 characters long." '    prompt user
        Password$ = GetPassword$ '                                         get password from user
    LOOP UNTIL LEN(Password$) > 7 '                                        continue if length ok
    PRINT " Please verify the password by typing it in again." '           prompt user
LOOP UNTIL Password$ = GetPassword$ '                                      continue if same pwd
PRINT '                                                                    blank line
PRINT " Remember for your records:" '                                      inform user
PRINT '                                                                    blank line
PRINT " Login    > "; Login$ '                                             display login name
PRINT " Password > "; Password$ '                                          display password
END '                                                                      end program

'-----------------------------------
'- Function and Subroutine section -
'-----------------------------------

'--------------------------------------------------------------------------------------------------

FUNCTION GetPassword$ ()

'**************************************************************************************************
'* Prompts the user for a password. As user types in password the keystrokes are displayed as     *
'* asterisks. The back space key is recognized. When the user presses the ENTER key the password  *
'* entered by the user is sent back to the calling routine.                                       *
'**************************************************************************************************

'---------------------------
'- Declare local variables -
'---------------------------

DIM Cursorline% ' current Y location of cursor
DIM CursorPos% '  current X location of cursor
DIM Password$ '   password created by user
DIM KeyPress$ '   records key presses of user

'------------------------
'- Function begins here -
'------------------------

PRINT " Password > "; '                                    prompt user for input
Cursorline% = CSRLIN '                                     save cursor Y position
CursorPos% = POS(0) '                                      save cursor X location
DO '                                                       begin main loop
    DO '                                                   begin key press loop
        _LIMIT 30 '                                        limit to 30 loops per sec
        KeyPress$ = INKEY$ '                               get key user presses
    LOOP UNTIL KeyPress$ <> "" '                           loop back if no key pressed
    IF ASC(KeyPress$) > 31 THEN '                          was key pressed printable?
        Password$ = Password$ + KeyPress$ '                yes, add it to password string
    ELSEIF ASC(KeyPress$) = 8 THEN '                       no, was it the back space key?
        Password$ = LEFT$(Password$, LEN(Password$) - 1) ' yes, remove rightmost character
    END IF
    LOCATE Cursorline%, CursorPos% '                       position cursor on screen
    PRINT STRING$(LEN(Password$), "*"); " "; '             print string of asterisks
LOOP UNTIL KeyPress$ = CHR$(13) '                          end main loop if ENTER pressed
PRINT '                                                    move cursor from end of asterisks
GetPassword$ = Password$ '                                 return the password user supplied

END FUNCTION

'--------------------------------------------------------------------------------------------------

Hidden Passwords
Figure 4 - Using string manipulation to create a hidden password function

-- Your Turn --










-- COMMAND REFERENCE --


Commands learned in previous tasks:

PRINT
INPUT
DIM
REM or '
CONST
TIME$
VAL()
IF...THEN
AND
END
GOTO
SELECT CASE...END SELECT
CASE
TO
IS
CASE ELSE
colon ( : )
FOR...NEXT
STEP
CLS
SLEEP
_DELAY
SYSTEM
DO...LOOP UNTIL (and variations)
WHILE...WEND
SCREEN
LINE
CIRCLE
PAINT
PSET
SUB
END SUB
FUNCTION
END FUNCTION
SHARED
SQR()
^ (exponent)
INPUT$
LINE INPUT
INKEY$
CHR$()
_KEYDOWN
_KEYHIT
_MOUSEX
_MOUSEY
_MOUSEINPUT
_MOUSEHIDE
_MOUSESHOW
_MOUSEMOVE
_MOUSEBUTTON
DIM
TYPE
END TYPE
AS

New commands introduced in this task:

UCASE$()
LCASE$()
LTRIM$()
RTRIM$()
INSTR()
STR$()
DATE$
LEFT$()
RIGHT$()
MID$()
ASC()
STRING$()
SPACE$()
SWAP

Concepts learned in previous tasks:

execute
statement
expression
literal string
syntax error
variables
type declarations
literal strings
concatenation
integers
long integers
single precision
double precision
strings
null strings
reserved words
operators ( +, -, *, / )
declare
constant
relational operators
functions
nesting
order of operations
Boolean
conditions
indenting
loops
labels
frame rate
controlled loops
conditional loops
Monochrome
Grayscale
pixels
bits per pixel
radian
counter-clockwise
aspect ratio
algorithm
subroutine
function
local variables
RAM
global variables
Pythagorean Theorem
BIOS
ASCII
buffer
CMOS
ROM
ASCII chart
array
one dimensional array
element
index
two dimensional array
three dimensional array

New concepts introduced in this task:

parse