News:

Function Finder  Find all the functions within source code files

Main Menu

Beginners Guide To Programming - Part VII - Strings

Started by kevin, April 21, 2008, 07:36:41 AM

Previous topic - Next topic

kevin


TDK's Play Basic Programming For Beginners

Back To Index


Part 7 - Everything You Wanted To Know About Strings (But Were Afraid To Ask)





 You wouldn't think so at first glance, but strings are quite important in every PlayBasic game you will write. After all, what good is a game which doesn't put messages onto the screen, let you talk to other characters or let you type your name into a highscore table. They all use strings!

 In PB, all strings are enclosed in double quotes ("") and are stored in string variables which are defined by having a dollar sign ($) on the end of them - as in the variable PlayerName$.

 Strings can be cut up, joined together (concatenation), searched, jumbled up and have sections in them replaced with something else.

 In this tutorial we'll be taking a look how to do all of these things. But first, a bit of background info...


 A character can be any alphanumeric symbol your computer can produce - either printable or not. A very simple example would be:

   
PlayBASIC Code: [Select]
A$="My Name Is Fred"


 When you put this in your program, PB will take everything inside the quotes and place them inside the string variable A$. The actual quotes are not stored - they are just markers so PB knows where the string starts and ends.

 The section in quotes is called a 'string literal' as opposed to A$ which is a string variable.

 You can add strings together:

PlayBASIC Code: [Select]
A$ = "ABC"
B$ = "DEF"
C$ = A$+B$
Print C$
Sync
Waitkey



This will print "ABCDEF" to the screen.

You can type the sentence 'My Name Is Fred' as it's an English sentence, however there may be times that you want to create a string of non-printable characters that aren't available on your keyboard.

There are a number of string-based commands in PB, which we'll cover later. One of them is CHR$() which we'll look at now...

Take our above example 'My Name Is Fred'. The first letter is a capital M. This, like the rest of the alphabet is an ASCII character and as such has an ASCII code.

The ASCII code for A is 65, B is 66, C is 67 and so on. As it happens, the code for M is 77 and if you tell PB to Print CHR$(77), then it will print an M on the screen.

Armed with a list of ASCII codes you could build up a string with CHR$():

 A$ = CHR$(77)+CHR$(121)+CHR$(32)+CHR$(78)+CHR$(97)+CHR$(109)+CHR$(101)

 All these CHR$()'s place 'My Name' into A$ and in this instance is a pointless exercise.   However, as mentioned a moment ago, you might want a string built up of characters that you can't type in.  In which case you could use this method:

PlayBASIC Code: [Select]
    A$=Chr$(240)+Chr$(241)+Chr$(242)+Chr$(243)+Chr$(244)
Print A$
Sync
Waitkey




 Each character in a string takes up one byte of memory and as a byte can store 256 numbers (0-255), a standard ASCII character set only has room for 256 characters.

 To interrogate a character and find out what it's ASCII code is, you can use the ASC() function:

  ASC("String") or ASC(StringVariable$) will return the ASCII code of a character so:

 Print ASC("A")

 ...will print 65 on the screen.

 ASC is meant to be used only on single characters as shown above, but you can actually use it with strings of more than one character - though in these cases only the first character is tested. Eg;

PlayBASIC Code: [Select]
Print ASC("Alan")
Print ASC("Andy")
Print ASC("Arthur")
Sync
Waitkey




 will all print the number 65. However, this can still be useful for a rudimentary string sort - based on the first character of each string.

 The simple code below will sort strings, but it can't be done with normal string variables - they have to be in a list using string arrays. Don't panic though - they are really easy when you get to know how they work...



String Arrays


  A string like Character1Name$ is a single entity. In a program, what if you had 3 characters with names?

 You would need Character1Name$, Character2Name$ and Character3Name$ to store them. You would also need a separate line of code referring to them for every occasion in your program.

 What if you had 100, 200 or even more characters? The answer is that normal strings would be impossible - you would have to use string arrays.

  Imagine an apartment block with 10 apartments. In reception on the wall there's a row of mailboxes numbered 1 to 10 for each of the apartments.

  The postman puts the mail for apartment 4 in box 4 and when the guy from apartment 4 comes down, he simply opens mailbox 4 for his mail.

  If all 10 mailboxes are collectively called 'Mailbox$' and the letters inside the boxes is our string data, then in effect we are describing a string array.

  The postie putting letters into mailbox 4, in DB terms is doing:

    Mailbox$(4) = Letters$

   And when the guy from apartment 4 gets his mail:

  Owner$ = Mailbox$(4)

 In PB, we just have to say how many mailboxes we require before we can start using them. This is called DIMensioning an array and with our mailbox example it would be:

DIM Mailbox$(9)

 Wait a minute!... Nine? You said 10 mailboxes!...

 Yes - that's correct I did. But, unlike our apartment block, PB has an apartment number 0. So for 10 actual boxes, we only need to DIM 9 which gives us boxes 0-9 - 10 in total.

 You can however use DIM Mailbox$(10) and ignore box 0 altogether if you find it easier. Just pretend that no-one lives in that apartment!

 So what's the point of all these boxes?...

 Well, the number in parenthesis () is called an index and as always when programming, you can replace a number with a variable. This means that instead of using Mailbox$(2) or Mailbox$(9), you can refer to Mailbox$(X). In turn, that means that you can use them in loops...

 Imagine printing the contents of 5 'mailboxes' the 'old' way:

PlayBASIC Code: [Select]
Print Mailbox1$
Print Mailbox2$
Print Mailbox3$
Print Mailbox4$
Print Mailbox5$



Now again with 100 mailboxes!

 No thanks... Try this instead:

(Sample code only)
PlayBASIC Code: [Select]
For X = 1 To 100
Print Mailbox$(X)
Next X



 Now you see the power of arrays!

 Actually, what we've been talking about so far is a 'single-dimension array' - just one row of boxes and one index number.

 However you can also have 'multi-dimensioned arrays' which are best thought of as being like a wall of lockers in a changing room. Say there is a stack of lockers ten wide and six high - sixty in total.

  The lockers running across might be numbered 1 to 10 and the rows running down labelled A, B, C and so on to F.

 If your stuff is in locker 5D, you need to count across to 5, then count 4 down to D to get to it.

 In PB, this translates to an array like Locker$(X,Y) where X is counting across and Y is counting down. Locker 5D would be;

 Locker$(5,4)


 Anyway, back to the issue in hand - sorting...

 Having our strings in an array means that we have an orderly way to present the new list after we've sorted it - in a loop using a variable for the array index number. Lets set up our example array:

PlayBASIC Code: [Select]
Dim Names$(10)

Names$(1) = "Geoff"
Names$(2) = "Tim"
Names$(3) = "Alison"
Names$(4) = "Pete"
Names$(5) = "Chris"
Names$(6) = "Barrie"
Names$(7) = "Nigel"
Names$(8) = "Rosie"
Names$(9) = "Simon"
Names$(10) = "Kevin"




(For simplicity, I've chosen to ignore element 0 of the array).

 OK, they aren't sorted at the moment. All we have to do is look at the ASCII codes of the first characters of strings 1 and 2.

 If the first string's ASCII code is greater than the second then it's higher in the alphabet and we need to swap their positions. We now repeat the process with strings 2 and 3, then 3 and 4 and so on.

 We use a 'DidWeSwap' flag and set it each time we have to do a swap when we run through the loop. If we run through the loop and a swap wasn't done, the flag isn't set and we know the strings are now all in the correct order.

 Now for the code (which is added to the end of the above snippet):

PlayBASIC Code: [Select]
Repeat
SwappedString = False
For N=1 To 9
Str1 = ASC(Names$(N))
Str2 = ASC(Names$(N+1))
If Str1 > Str2
Temp$ = Names$(N)
Names$(N) = Names$(N+1)
Names$(N+1) = Temp$
SwappedString = True
Endif
Next N
Until SwappedString = False

For N=1 To 10
Print Names$(N)
Next N
Sync
WaitKey




 As you can see, a complete run though the array is within the Repeat..Until loop. We reset the SwappedString flag (to False) at the start of each run through this loop and check to see if it's been set at the end.

 If it has been set (to True) then we've not finished so repeat the loop again. If it's still False then no swaps were made during that pass and the list is sorted - so drop out of the loop.

 The only other thing of note is the use of the string variable Temp$. This is used during the swap process because we don't want to lose string 1 when we copy string 2 into it. We copy string 1 into Temp$, string 2 into string 1 and then Temp$ into string 2.

 The last For..Next loop prints out the 10 strings in the array and if you run the program you will see that all the names are in sorted in alphabetical order.

 This simple sort routine is called a bubble sort because strings in the list that are not in their correct positions 'bubble' to the top. Of all of the sorting methods, this one is probably the easiest to program, but least efficient.


  At this point let's introduce a few more PB string commands before we need them...


Left$(), Mid$(), Right$() and Len()


Left$(StringVar$,NumChars) will extract NumChars characters from StringVar$ starting at the left. So:

PlayBASIC Code: [Select]
A$="ABCDEFGHIJK"
Print Left$(A$,5)
Sync
Waitkey



...will print 'ABCDE' on the screen - the leftmost 5 characters.


Right$(StringVar$,NumChars) will extract NumChars characters from StringVar$ starting at the right. So:

PlayBASIC Code: [Select]
A$="ABCDEFGHIJK"
Print Right$(A$,5)
Sync
Waitkey



...will print 'GHIJK' on the screen - the rightmost 5 characters.


Mid$(StringVar$,CharPos,NumberOfCharacters) will extract the characters at position Charpos in StringVar$. So

PlayBASIC Code: [Select]
A$="ABCDEFGHIJK"
Print Mid$(A$,6,1)
Sync
Waitkey



...will print 'F' on the screen - the character at position 6.


Len(StringVar$) will return the length of StringVar$. So

PlayBASIC Code: [Select]
A$="ABCDEFGHIJK"
Print Len(A$)
Sync
Waitkey



...will print '11' on the screen - the length of A$.


 How about writing word puzzle games like anagrams?

 Using the above commands we can take a string and jumble all the letters in it:

PlayBASIC Code: [Select]
GameWord$ = "elephant"
ShowWord$ = ""
WordLen = Len(GameWord$)
For N=1 To WordLen
RandChar = Rnd(Len(GameWord$)-1)+1
ShowWord$ = ShowWord$ + Mid$(GameWord$,RandChar,1)
GameWord$ = Left$(GameWord$,RandChar-1)+Right$(GameWord$,Len(GameWord$)-RandChar)
Next N

Print ShowWord$
Sync
WaitKey





 For this example, we set GameWord$ as the word to scramble, (though in a real situation, words could be selected at random from a pre-loaded text file, or read from Data lines).

Next we get the length of the word and store it in WordLen.

 The main For..Next loop counts from 1 to the length of the word - 8 with the word elephant. The RandChar line picks a random number which is always between 1 and the number of letters in GameWord$. It's done like this because in the following lines, the length of GameWord$ will change.

 After selecting the random number, the character at that position in GameWord$ is added to ShowWord$.

 Finally, the chosen letter is removed from GameWord$ so it won't be chosen again.

 The loop is repeated so, by the end of the loop, GameWord$ has been reduced from 8 characters to an empty string and ShowWord$ has gone from an empty string to being 8 characters long - but a jumbled version of the word.

You could then display the word and time how long it takes the user to enter the correct word - awarding more points, the quicker it's done.


 How about highscore tables then...

 A highscore table also uses string array lists - usually two of them: one for the name and one for the score.

 The only tricky part of creating a hiscore list is deciding where to insert an entry at the end of a game. So let's cover the theory before tackling the code...

  OK, we start with the name and score arrays both empty. When the game has ended and we have a final score, we simply loop through the score array starting at the bottom comparing the player's score with the score in the array.

  If the array entry is smaller than the player's score then we continue round the loop.

 If however the array entry is greater than the player's score - or if the hiscore table is empty and we reach the top of the array list - then we have reached the required 'slot'. All we need to do is shuffle all the entries below it down one in both arrays - with the last entries on both lists 'dropping off the end'.

 All that is required then is to feed our new player information into the array at the calculated position.

 Once a highscore table is created it's saved to disk and loaded when required.

 So how is this done in PB?...

PlayBASIC Code: [Select]
DIM HiScoreName$(10)
DIM HiScoreValue$(10)

PlayerScore = 3500
PlayerName$ = "TDK_Man"

Rem Fill Array With Existing Scores
For N=1 To 10
HiScoreName$(N) = "PlayerName"
HiScoreValue$(N) = Str$((11-N)*1000)
Next N

Rem *******************************************
N=11
Repeat
Dec N
ArrayScore = VAL(HiScoreValue$(N))
Until ArrayScore > PlayerScore or N=0
Rem N is now the slot above the one we actually want (which is N+1)
Rem so we shuffle all it and all below it down to make room
For I=10 To N+2 Step -1
HiScoreName$(I) = HiScoreName$(I-1)
HiScoreValue$(I) = HiScoreValue$(I-1)
Next I
Rem Slot N+1 is now free so fill it
HiScoreName$(N+1) = PlayerName$
HiScoreValue$(N+1) = Str$(PlayerScore)
Rem *******************************************

Rem Now print out the new list of 10 names
For N=1 To 10
Print Str$(N)+". "+HiScoreName$(N) + " - " + HiScoreValue$(N)
Next N

Sync
WaitKey

end





 The important code is inside the Rem lines of stars.

 The Repeat..Until loop starts at the bottom of the hiscore list reading the score values in the array until the score found is greater than the score just made by the player - or the counting loop variable N = 0 (at which point all the array has been checked).

 If a higher number is found then the loop is exited with N containing the number of the array element of the slot directly above the one we need to put our score info into. So, we need to move all the elements of the array down one - including the one we need to fill.

  So entry 10 gets replaced with entry 9, entry 9 gets replaced with entry 8 and so on until we reach the slot we want.

  If we want to use slot 4 for example, that slot will be N+1 so the last move in the For..Next loop will be moving the contents of N+1 into N+2 - leaving slot N+1 (4) free.

Finally we place the players name and score into the respective arrays at position N+1 - and in your game, write them to a file on disk.

 You can alter the value on the PlayerScore = 3500 line before running the program to confirm that the correct slot is always selected.


 Armed with the commands we've seen in this tutorial, we can also do some pretty unusual stuff with strings.

  To end this tutorial, here's a novel way to tackle a task in many games with enemies or characters which have health points that can vary as you play. All you have to do is look at strings in a slightly different way...

  As a string is simply a long joined list of numbers - just like a numeric array, you can treat them as such - you just need to remember that being a byte, the maximum size for the numbers is 255.

 The following example may not be the best way to do what it demonstrates, but it serves nicely as an example of the process using strings - which you can adapt to other things.

 So, say you have 10 enemies in your game and their health values vary from 100 (full health) to 0 (dead). At the start of your program, you use:

  EnemyHealth$="dddddddddd"

  What!!?? I hear you say...

  Well, d just happens to have the value of 100 in the ASCII table and in the above line there are 10 d's - one for each of the enemies.

  So what do we do with it?

  Well, our string currently contains 10 d's - each having the value of 100. The first 'd' belongs to enemy 1, the second to enemy 2, the third to enemy 3 and so on.

  Say you give enemy 3 a smack in the eye and his health is reduced by 3 points.

 All we have to do is get the current ASCII value of the third character in our health string, reduce it by 3, turn it back into a character and put it back into the string.

+ Code Snippet
PlayBASIC Code: [Select]
 EnemyHealth$ = "dddddddddd"
EnemyNumber = 3
HitPoints = 3
EnemyHealthVal = ASC(Mid$(EnemyHealth$,EnemyNumber,1))
Before$ = Left$(EnemyHealth$,EnemyNumber-1)
After$ = Right$(EnemyHealth$,Len(EnemyHealth$)-EnemyNumber)
EnemyHealthVal=EnemyHealthVal-HitPoints
EnemyHealth$ = Before$ + Chr$(EnemyHealthVal) + After$
Print EnemyHealth$
Sync
WaitKey





 I've kept the coding as simple as possible so you can follow it and see exactly what it's doing.

 The first three lines simply set up the variables.

 The fourth line look more complicated than it actually is. All it does is use Mid$() to get the character in EnemyHealth$ which corresponds to our enemy (number 3). When we have it, we convert it to a number using ASC() - this gives us 100 and store it in EnemyHealthVal.

 The fifth line grabs the first two characters of EnemyHealth$ into Before$. Remember, we are only interested in the third character (enemy). If EnemyNumber is 3 then using EnemyNumber -1 with Left$ will grab the first 2 characters.

 The next line needs to grab all of the characters after the third one and store them in After$. It does this by using RIght$().

 Len(EnemyHealth$) returns 10 (the length of the whole string) and deducting the number of our enemy (3) gives us the number of characters to grab from the right using Right$(). In this case, 10-3 gives us 7 characters - which we store in After$.

 Next, we deduct HitPoints (3) from EnemyHealthVal (100) which gives us 97.

 The last important line builds up the new EnemyHealth$ by adding together: Before$ (the first two characters), the new health of enemy 3 which we convert back to a string with Chr$(EnemyHealthVal) and After$ (the remaining seven characters).

 This equates to:

   "dd"+"a"+"ddddddd"

  which you will see if you run the above snippet.

 I'm not suggesting that this is either the best or fastest method to use for this particular task - I'm just using it as an example of this particular way of using strings.

  I use the same method to deal from a shuffled pack of cards in card games by creating four string variables - one for each suit (H, C, D and S). For hearts, the string would be:

 Hearts$ = "1H2H3H4H5H6H7H8H9HTHJHQHKH"

  Each card is two characters - the second being the suit and the first being the numbers 1 to 9, T for ten, J for Jack, Q for Queen and finally K for king. The other three suits are stored as:

Clubs$ = "1C2C3C4C5C6C7C8C9CTCJCQCKC"
Diamonds$ = "1D2D3D4D5D6D7D8D9DTDJDQDKD"
Spades$ = "1S2S3S4S5S6S7S8S9STSJSQSKS"


Next we join them together to form a pack:

Pack$ = Hearts$+Clubs$+Diamonds$+Spades$

The length of Pack$ is 104 characters long (52 cards of 2 characters each).

When we deal from the pack, we pick a random number based on the length of Pack$. Multiplying that random number by 2 and adding 1 gives us the position in Pack$ of the two characters for that card. Eg:

With a new pack = (104/2)-1 = 51
Get random number between 0 and 51 - let's say the number 8 comes up (the ninth card).
(8*2)+1=17
The 17th character in Pack$ is 9 and the 18th character is H so the card dealt is 9H - the 9 of Hearts.

Once dealt, we use the method used above to remove the 17th and 18th characters from the pack so the card can't be dealt again.

The next time a random number is required, the length of Pack$ has been reduced by 2 so it's now:

(102/2)-1 = 50 - which gets a random number between 0 and 50.

When all the pack has been dealt, restore Pack$ by adding the four suit strings together again and off you go...


Well that's it for this tutorial on strings. I hope it's covered all the topics you wanted it to.



TDK_Man