Tuesday, 22 January 2019

Web Tutorial: The Zombies Run Voiceover

Following several years of playing Zombies, Run!, an urge to duplicate the in-game voiceover hit me. You know the voice that informs you of what you've collected every now and then? Yeah, that one.

Perhaps it was acute boredom or professional curiosity, but that itch would not go away... and today's web tutorial is the result. I really got a kick out of this piece of work, and hope you do too. It's basically a lot of string concatenation in conjunction with the VBScript code I wrote a while back.

So, the objective is to have a program that will say random things like...

"Collected a toolbox."

"Collected a toolbox and an overcoat."

"Collected a toolbox, a box of lightbulbs and two spades."

"Collected a toolbox, a sports bra, three books, five footballs and four handfuls of money."

Hold on, what does this actually do?

Nothing useful, I'm afraid. It's an utterly trivial task. I'm like a kitten with a ball of wool at this point.

Without further ado...

Let's begin with the speech code and a randomizer function. Simple enough, right? Upon running it, you get a voice that says "Collected a", and that's it.
Dim speech

Function getrandomno(min, max)
    Randomize
    getrandomno = (Int((max - min + 1) * Rnd + min))
End Function

speech = "Collected a "

Set VObj = CreateObject("sapi.spvoice")
VObj.Speak speech


Now, you'll want to actually have the voice tell you what items you picked up. Declare two more variables, item and item_name. Then declare an array, items, with four elements. Each of these will be an object you picked up.
Dim speech
Dim item, item_name
Dim items(4)
items(0) = "spade"
items(1) = "toolbox"
items(2) = "sports bra"
items(3) = "shotgun"

Function getrandomno(min, max)
    Randomize
    getrandomno = (Int((max - min + 1) * Rnd + min))
End Function

speech = "Collected a"

Set VObj = CreateObject("sapi.spvoice")
VObj.Speak speech


Finally, set item to a random number between 0 and 3. Then set item_name to the string inside the array items, defined by item. Remember to add a space before it! After that, concatenate item_name to speech. Now the voice should say stuff like "Collected a toolbox" or "Collected a spade", randomly!
Dim speech
Dim item, item_name
Dim items(4)
items(0) = "spade"
items(1) = "toolbox"
items(2) = "sports bra"
items(3) = "shotgun"

Function getrandomno(min, max)
    Randomize
    getrandomno = (Int((max - min + 1) * Rnd + min))
End Function

item = getrandomno(0, 3)
item_name = " " & items(item)

speech = "Collected a"

speech = speech & item_name

Set VObj = CreateObject("sapi.spvoice")
VObj.Speak speech


Yep, give yourself a pat on the back. You've just created the core of what is to be our totally spectacular (but still utterly useless, heh heh) program. Now, not everything is prefaced with "a". You can have "a spade" or "a shotgun", but not "a overcoat". It's simply not something you can take for granted, the English language being what it is. With that in mind, some changes are required.

Change items to a two-dimensional array. The first element is the item name, and the second is the "a" or "an".
Dim items(4)
items(0) = Array("spade", "a")
items(1) = Array("toolbox", "a")
items(2) = Array("sports bra", "a")
items(3) = Array("shotgun", "a")


Let's add a new item to the array items.
Dim items(5)
items(0) = Array("spade", "a")
items(1) = Array("toolbox", "a")
items(2) = Array("sports bra", "a")
items(3) = Array("shotgun", "a")
items(4) = Array("overcoat", "an")


Declare a new variable, quantity, that will hold either "a" or "an".
Dim speech
Dim item, item_name, quantity


Also ensure that you now get a random number from 0 to 4 instead. And set quantity to the first element of the array element in items referenced by item!
item = getrandomno(0, 4)
quantity = items(item)(1)


Change the following lines. Now instead of just referencing one array element from items, we derive item_name from the first array element of that array element, and quantity! And we remove the hardcoded "a" from speech. So now, sometimes we'll get "Collected an overcoat"!
item_name = " " & quantity & " " & items(item)(0)

'speech = "Collected a"
speech = "Collected"


Let's take this further. What if sometimes we want a collective item instead of singular? Like, "a box of lightbulbs" or "a handful of money"?

Simple. We extend the items array, by making each item an array of four elements instead of two. The third element is the collective noun, and the fourth is the "a" or "an". In the latest two elements we've added, the second element is always an empty string. With singular items, the third and fourth elements are always empty strings.
Dim items(7)
items(0) = Array("spade", "a", "", "")
items(1) = Array("toolbox", "a", "", "")
items(2) = Array("sports bra", "a", "", "")
items(3) = Array("shotgun", "a", "", "")
items(4) = Array("overcoat", "an", "", "")
items(5) = Array("lightbulbs", "", "box", "a")
items(6) = Array("money", "", "handful", "a")


Add a new variable, unit.
Dim speech
Dim item, item_name, quantity, unit


Make sure the random number is now between 0 to 6. unit is initially an empty string.
item = getrandomno(0, 6)
quantity = items(item)(1)

unit = ""


Now, if the third element of the array element is not an empty string, that means it requires a unit. In this case, set unit to the value of that third element and concatenate " of" to it. Set quantity to the fourth element instead of the second.
item = getrandomno(0, 6)
quantity = items(item)(1)

unit = ""

if items(item)(2) <> "" then
    unit = " " & items(item)(2) & " of"
    quantity = items(item)(3)
end if


Then concatenate unit between quantity and the item name (ensuring there are spaces between them as well), to get item_name! Now you should occasionally get "Collected a box of lightbulbs" or "Collected a handful of money".
item = getrandomno(0, 6)
quantity = items(item)(1)

unit = ""

if items(item)(2) <> "" then
    unit = " " & items(item)(2) & " of"
    quantity = items(item)(3)
end if

item_name = " " & quantity & " " & unit & " " & items(item)(0)

Now for something trickier...

What if you wanted plural items? Like "spades" instead of "spade"? Not every plural simply has an "s" appended to the end. "Boxes" and "Hippopotami" are strong examples.

For this, we'll need to modify the items array again. This time, each element should be an array of five elements instead of four. The fifth element (here's looking at you, Bruce Willis!) will hold the plural form of the item name, or the plural form of the collection.
items(0) = Array("spade", "a", "", "", "spades")
items(1) = Array("toolbox", "a", "", "", "toolboxes")
items(2) = Array("sports bra", "a", "", "", "sports bras")
items(3) = Array("shotgun", "a", "", "", "shotguns")
items(4) = Array("overcoat", "an", "", "", "overcoats")
items(5) = Array("lightbulbs", "", "box", "a", "boxes")
items(6) = Array("money", "", "handful", "a", "handfuls")


Then we set quantity to a random number between, say, 1 to 5.
item = getrandomno(0, 6)
quantity = getrandomno(1, 5)


At the If block, we add an Else condition, and surround the entire block with another If block.
if quantity = 1 then
    if items(item)(2) <> "" then
        unit = " " & items(item)(2) & " of"
        quantity = items(item)(3)
    else
        quantity = items(item)(1)
    end if
else

end if


quantity is singular, so we'll replace the value of quantity with the appropriate "a" or "an". item_name is, quite naturally, the first element of the array.
if quantity = 1 then
    if items(item)(2) <> "" then
        unit = " " & items(item)(2) & " of"
        quantity = items(item)(3)
    else
        quantity = items(item)(1)
    end if

    item_name = items(item)(0)
else

end if


If quantity is more than 1, we use the plural form (the fifth element). And instead of "a" or "an", we use the number! So, no changes to quantity.

If the item is collective, we use the plural form of the collective noun. If not, we simply use the plural form of the item.

Now, you're going to get stuff like "Collected 5 shotguns" or "Collected 3 boxes of lightbulbs", in addition to the ones you already have!
unit = ""

if quantity = 1 then
    if items(item)(2) <> "" then
        unit = " " & items(item)(2) & " of"
        quantity = items(item)(3)
    else
        quantity = items(item)(1)
    end if   

    item_name = items(item)(0)
else
    if items(item)(2) <> "" then
        unit = " " & items(item)(4) & " of"
        item_name = items(item)(0)
    else
        item_name = items(item)(4)
    end if
end if


For the final trick...

What if you wanted different items in different quantities? Like, "a spade and three boxes of lightbulbs"? This one gets a bit more complicated, so I'll need you to pay attention.

First, we need a new variable, no_items.
Dim speech
Dim item, item_name, quantity, unit, no_items


Then we need two new arrays, collections and used. We'll arbitrarily assume that the largest number of different item types will be 5.
Dim speech
Dim item, item_name, quantity, unit, no_items
Dim collections(5), used(5)


After the getrandomno() function, we're going to set no_items to a number between 0 and 4.
Function getrandomno(min, max)
    Randomize
    getrandomno = (Int((max - min + 1) * Rnd + min))
End Function

no_items = getrandomno(0, 4)


Now, wrap all that code we wrote earlier, in a For loop iterating from 0 to no_items. The steps will be there - we're just going to repeat it i number of times.
no_items = getrandomno(1, 4)

for i=0 to no_items step 1
    item = getrandomno(0, 6)
    quantity = getrandomno(1, 5)

    unit = ""

    if quantity = 1 then
        if items(item)(2) <> "" then
            unit = " " & items(item)(2) & " of"
            quantity = items(item)(3)
        else
            quantity = items(item)(1)
        end if   

        item_name = items(item)(0)
    else
        if items(item)(2) <> "" then
            unit = " " & items(item)(4) & " of"
            item_name = items(item)(0)
        else
            item_name = items(item)(4)
        end if
    end if

    item_name = " " & quantity & " " & unit & " " & item_name
next


At the end of it, set the current element of the collections array to the value of item_name.
no_items = getrandomno(1, 4)

for i = 0 to no_items step 1
    item = getrandomno(0, 6)
    quantity = getrandomno(1, 5)

    unit = ""

    if quantity = 1 then
        if items(item)(2) <> "" then
            unit = " " & items(item)(2) & " of"
            quantity = items(item)(3)
        else
            quantity = items(item)(1)
        end if   

        item_name = items(item)(0)
    else
        if items(item)(2) <> "" then
            unit = " " & items(item)(4) & " of"
            item_name = items(item)(0)
        else
            item_name = items(item)(4)
        end if
    end if

    item_name = " " & quantity & " " & unit & " " & item_name

    collections(i) = item_name
next


After setting speech to "Collected", instead of concatenating one item_name to speech, we add all the strings from the collections array. Use a For loop to iterate from 0 to no_items.
speech = "Collected"

for i = 0 to no_items step 1

next

'speech = speech & item_name


If we're not at the end of the list of items, concatenate the value of the current element of collections and a comma to speech.
for i = 0 to no_items step 1
    if i < no_items then
             speech = speech & collections(i) & ","
    else

    end if
next


If we're at the end of the list, there are two possibilities. One, there is only one item type in the collections array, i,e, i is 0. The other is that there are multiple item types and we're at the end.
for i = 0 to no_items step 1
    if i < no_items then
             speech = speech & collections(i) & ","
    else
        if i = 0 then
       
        else
       
        end if
    end if
next


If it's the former, just concatenate the value of the current element of collections to speech. If there were more than one item and we're at the end, concatenate to speech the string " and" (take note of the space!) followed by the value of the current element of collections.
for i = 0 to no_items step 1
    if i < no_items then
             speech = speech & collections(i) & ","
    else
        if i = 0 then
            speech = speech & collections(i)
        else
            speech = speech & " and" & collections(i)
        end if
    end if
next


Now try it!

For the most part, you'll get beautifully constructed speeches like "Collected a sports bra, three spades, two boxes of lightbulbs and a handful of money". But wait... there's a problem. Once in a while, the randomizer repeats itself. So sometimes you may get something like "Collected a spade and two spades". That's not what we want at all, so...

Here, the used array will store all the unique numbers that have been generated.

Remember the line to randomize what item you get? Well, wrap that in a Do-while loop. It should execute at least once. And keep executing while the returned value of the inuse() function with item passed in as an argument, is true. So if the loop is exited, item is guaranteed to be unique within the used array.

And then set the current element of the used array to item.
for i = 0 to no_items step 1
    do while inuse(item) = true
        item = getrandomno(0, 6)
    loop

    used(i) = item

    quantity = getrandomno(1, 5)


Then let's write the inuse() function. This basically tells you if item is already taken.
Function getrandomno(min, max)
    Randomize
    getrandomno = (Int((max - min + 1) * Rnd + min))
End Function

Function inuse(num)

End Function


The parameter is num. Declare dupe as a variable and set it to false. At the end of the function, return dupe.
Function inuse(num)
    Dim dupe

    dupe = false

    inuse = dupe
End Function


Run a For loop that iterates through no_items. If at any point the value of the current element of used matches num, dupe is set to true.
Function inuse(num)
    Dim dupe

    dupe = false

    for j = 0 to no_items step 1
        if used(j) = num then
            dupe = true
        end if
    next

    inuse = dupe
End Function


There, you've got it!

Just for shits and giggles, let's add a whole lot more stuff into the items array!
Dim items(15)
items(0) = Array("spade", "a", "", "", "spades")
items(1) = Array("toolbox", "a", "", "", "toolboxes")
items(2) = Array("sports bra", "a", "", "", "sports bras")
items(3) = Array("shotgun", "a", "", "", "shotguns")
items(4) = Array("overcoat", "an", "", "", "overcoats")
items(5) = Array("lightbulbs", "", "box", "a", "boxes")
items(6) = Array("money", "", "handful", "a", "handfuls")
items(7) = Array("axe", "an", "", "", "axes")
items(8) = Array("trousers", "", "pair", "a", "pairs")
items(9) = Array("food", "", "tin", "a", "tins")
items(10) = Array("book", "a", "", "", "books")
items(11) = Array("fuel", "", "can", "a", "cans")
items(12) = Array("pain meds", "", "bottle", "a", "bottles")
items(13) = Array("football", "a", "", "", "footballs")
items(14) = Array("radio", "a", "", "", "radios")


Don't forget to change the random number generator code!
item = getrandomno(0, 14)


Have fun with your program! Better yet, if you haven't already, check out Zombies, Run!.

Collected a thank-you, good-bye and a see-you-again!
T___T

No comments:

Post a Comment