Jump to content

Random scripting


Archaon

Recommended Posts

[lurk mode off]

 

I was wondering whether there is a way to get a numerical input from the player. The only thing I could find in the docs was the get_text_response call. Needless to say, it's hideously complicated. Plus I couldn't find a call that would extract a numerical variable from a string.

 

Is there some reserved string that secretly stores the get_text_response or is the only way something messy like this:

 

Code:
variables;string strcom;short i, found;beginstate 10;get_text_response("Give me number!");while (i <= 1000){ clear_buffer(); append_number(i); get_buffer_text(strcom); check_text_response_match(strcom); if (got_text_match() == 1){  found = i;  i = 1000; } i = i + 1;}print_num(found);break;

 

Yes, I am aware this would go way over the loop limit, but that can be circumvented later. Can you think of another way?

Link to comment
Share on other sites

Your way doesn't work because you can't compare a string to a number, even if the string contains a number.

 

There are two methods for doing what you want to do:

 

1) Have a big list of special-case checks for each individual number within the range you expect players to input. So you check if the text input is equal to the string "1", then if it's equal to the string "2", and so on. The High Level Party Maker contains a list like this already (t2Raiser.txt, state 12) so you can pretty much just copy-paste the relevant section of script. (Don't worry, the author encourages this.)

 

2) Have the player enter the number one digit at a time, check what the digit is (using method 1) and turn the individual digits into a number at the end.

 

Both methods suck, but in slightly different ways.

Link to comment
Share on other sites

Yes, Archaon, you can make this method work, it's the same one that I use in Magic Lab:

Code:
get_text_response(prompt);what_num = -17000;x = -255;while(x<256){	clear_buffer();	append_number(x);	get_buffer_text(num_buf);	check_text_response_match(num_buf);	if (got_text_match()){		what_num = x;		x = 1000;	}	x = x + 1;}

I specialized my code to numbers in the range [-255,255] so that it would stay well within the 'node' limit, and because that was all I needed. I suspect that the range could still be expanded a good deal, likely up to and including what you've shown, but I'd have to have to take a few minutes to calculate the 'node' usage.

 

Thuryl, I'm not sure you actual read what he's doing. The basic idea is sound.

 

EDIT: Looking more closely myself, maybe you were thrown off by his forgetting the parentheses needed by got_text_match, making it look like a variable.

Link to comment
Share on other sites

Thanks for the response people (This was also a test to gauge the community’s presence ;-).

 

Yes I did forget those parentheses there(added). I wasn’t comparing number to string, that’s the whole limitation we’re trying to overcome. I think the best way would be something combining what I did with the ‘one by one digit’ method.

 

Btw, what exactly is the nesting limit of avernumscript? I’m sure I’ve read it somewhere (total amnesia…) and I sure managed to hit it while trying to scan a large town in one go instead of breaking it up. Is it possible to fool the compiler by using a self-calling state instead of a while loop?

Link to comment
Share on other sites

The limit is 32000 'nodes', where a 'node' is roughly equivalent to a statement, see this post for more details.

 

Unfortunately, there's not much you can do to fool the interpreter due to its very simplistic nature; when it begins executing a script it starts counting, and since things like switching states are just jumps around its byte code stream, it just keeps on counting. The only way that really works to trick it is to rely on the fact that it resets its count when executing a different script. So, if you can split the work across many scripts, it doesn't notice. One way to implement this, which I've used, is to have a bunch of dummy terrain scripts which exist solely to make calls to the town script. The town script has the code broken into blocks, and keeps track of which block it needs to do next so that the order in which the terrain scripts run is irrelevant. The net effect is that the town script runs a lot, but the 'cost' is divided among the many dummy scripts. One thing to keep in mind is that, given the slowness of the interpreter, you really don't want to be doing something like this dodge to run lots of code every tick, as the game will run like molasses.

Link to comment
Share on other sites

So, if I got this right I could optimize the while loop like this:

Code:
     clear_buffer();    append_number(0);    get_buffer_text(strcom);    check_text_response_match(strcom);    i = 0;while ((i <= 1000) && (got_text_match != 1)){    i = i + 1;     clear_buffer();    append_number(i);    get_buffer_text(strcom);    check_text_response_match(strcom);    }    found = i; 

And get 7 nodes per loop, which would comfortably allow detection a bit beyond 4500…

 

About the node count, does it reset every time a different script runs, or does the initial script need to stop first? For example, you can call a town state in the middle of a terrain script and resume executing it after the town state is done. Does that reset the count of the terrain script? What about the count of the town script?

 

Link to comment
Share on other sites

Quote:
For example, you can call a town state in the middle of a terrain script and resume executing it after the town state is done. Does that reset the count of the terrain script? What about the count of the town script?

It looks like in this case the game keeps a separate count for each script, but running the town script does not make it forget the count for the terrain script. I probably shouldn't have used the word 'reset', above, and should have phrased it in terms of separate counts.

A piece of good new is that the count for the town script is forgotten in between invocations of run_town_script(). So, given a town script state like this:
Code:
beginstate 11;	print_str("Town::11");	x=10665;        while(x>0){                x = x - 1;        }break;

I was able to get away with doing this:
Code:
beginstate SEARCH_STATE;	print_str("SEARCH_STATE before");	run_town_script(11);	print_str("SEARCH_STATE between");	run_town_script(11);	print_str("SEARCH_STATE after");break;


This is much better than the overly paranoid way I was doing things before, and means that the upper limit on the amount of computation that can be done in a single round is not (maximum number of terrain scripts)*32000 nodes=3.2 million nodes, but more like a billion(!) nodes per terrain script, making the upper limit (ignoring use of creature scripts) 100 billion nodes. So, there's no need to worry over much about the upper ceiling as long as all computation can be broken into 32000 node chunks.

(Of course, nobody wants to spend a week waiting while your scenario sits there thinking, and it might also be hard to actually reach the billion nodes per script limit due to other problems like restrictions on the sizes of scripts.)
Link to comment
Share on other sites

My method uses SDFs to record individual digits of a number, hence it has no real upper limit. You must be careful to enter the digits one at a time.

beginstate 77;

i = 0;

while (i < 5) {

get_text_response("What digit: 0 - 9?");

check_text_response_match("0");

if (got_text_match()){

set_flag(150,i,0);

}

check_text_response_match("1");

if (got_text_match()){

set_flag(150,i,1);

}

check_text_response_match("2");

if (got_text_match()){

set_flag(150,i,2);

}

check_text_response_match("3");

if (got_text_match()){

set_flag(150,i,3);

}

check_text_response_match("4");

if (got_text_match()){

set_flag(150,i,4);

}

check_text_response_match("5");

if (got_text_match()){

set_flag(150,i,5);

}

check_text_response_match("6");

if (got_text_match()){

set_flag(150,i,6);

}

check_text_response_match("7");

if (got_text_match()){

set_flag(150,i,7);

}

check_text_response_match("8");

if (got_text_match()){

set_flag(150,i,8);

}

check_text_response_match("9");

if (got_text_match()){

set_flag(150,i,9);

}

i = i + 1;

}

set_state_continue(78);

break;

 

beginstate 78;

j = 0;

// This is for checking faults only.

while (j < 5) {

k = get_flag(150,j);

print_big_str("The digit you chose was ",k,".");

j = j + 1;

}

set_state_continue(79);

 

break;

 

beginstate 79;

l = (10000 * get_flag(150,0)) + (1000 * get_flag(150,1)) + (100 * get_flag(150,2)) + (10 * get_flag(150,3)) + (get_flag(150,4));

print_big_str("The value for l is ",l,".");

break;

Link to comment
Share on other sites

Quote:
Niemand is a genius.

I doubt I deserve such praise, but thanks.

Quote:
does Avernumscript allow typecasting?

No. It's kind of moot since there are only three datatypes, one of which has two interchangeable names (int|short), one of which cannot be manipulated directly in any way (string), and one of which is a useless vestige of a feature that never came to be (location).
Link to comment
Share on other sites

Originally Posted By: Ishad Nha
Typecasting &c,
Fixed; & alone means "and", but &c means "etcetera".

Originally Posted By: Ishad Nha
Avernumscript is a very much watered-down version of C,
Understatement. The only thing it shares with C is some basic syntax (if statements, while statements, basic arithmetic expressions, semicolon-terminated statements).
Link to comment
Share on other sites

Not just an understatement, a totally false statement. It isn't a version of C in any way, shape, or form. It shares basic syntax with innumerable scripting and programming languages. Heck, the way some of it is structured, and given Jeff's preferences, I wouldn't be surprised if hypertalk was an influence.

Link to comment
Share on other sites

Ok, I’m having the beginnings of an idea and I’ve been messing around with the string buffer. I’ve been running this:

Code:
 Variables;Short  i, j;String testor;beginstate 15;   clear_buffer();   j = 0;   while (j <= 40){      j = j + 1;      i = 0;      while (i != 9){         i = i + 1;         append_number(i);      }  append_string(" ");   }get_buffer_text(testor); clear_buffer();  //deals with unhandled exceptionmessage_dialog(testor,"");break; 

 

It basically overflows the buffer with 1-9 and a gap, then passes it in a string variable and prints it in a text box. My results:

 

a)At first I got an unhandled exception. (I’m running windows XP(2003) at 64bit). I noticed, however that the exception would trigger about a dozen ticks from when I’d run the script. By adding a clear_buffer() as soon as I was done retrieving the data, the problem was solved.

 

b)I have read in the bug list that strings can actually carry 254 characters. I don’t know if it’s outdated or if I messed up somewhere, but the above code stopped printing at 5, meaning it reached 255 characters. Can anyone confirm this? Will it work the same on a Mac?

Link to comment
Share on other sites

Although I don't know if Jeff ever mentions it in the manual, the language is totally case-insensitive.

 

Code:
// Goes through text and eliminates all commented text and makes all text not in quotes// lowercasevoid text_block_type::preprocess_text(){	if (block_length <= 0)		return;			Boolean in_comments = FALSE;	for (long i = 0; i < block_length; i++) 		if (text_block[i] == 10)			text_block[i] = 13;	for (long i = 0; i < block_length; i++) {		if ((i < block_length - 1) && (text_block[i] == '/') && (text_block[i + 1] == '/'))			in_comments = TRUE;			else if (text_block[i] == 13)				in_comments = FALSE;		if (in_comments == TRUE)			text_block[i] = ' ';		}	Boolean in_quotes = FALSE;	for (long i = 0; i < block_length; i++) {		if (text_block[i] == '"')			in_quotes = !(in_quotes);		if ((in_quotes == FALSE) && (text_block[i] >= 65) && (text_block[i] <= 90))			text_block[i] = text_block[i] - 'A' + 'a';		}}
Link to comment
Share on other sites

Well, it is brute force, in the sense that you cycle through all numbers between 0 and the target, but you don’t actually need to write all number strings yourself when a while loop can do it instead ;-)

 

Is there a way to shove the get_text_response() into a proper string without doing thousands of loops? The documentation failed me there. I have thought of fancy ways to count the length of a string and even break it into individual letters, but without a way to easily get a string it’s pretty useless.

Link to comment
Share on other sites

Quote:
Is there a way to shove the get_text_response() into a proper string without doing thousands of loops? The documentation failed me there. I have thought of fancy ways to count the length of a string and even break it into individual letters, but without a way to easily get a string it’s pretty useless.

There is no way to do this officially provided. There are ways involving dark magics. Specifically there is a way, which works very nicely for this purpose. I have not quite completed the research necessary to make this generally applicable, and using the aforementioned dark magics heightens the fragility of the scenario in which they are applied, in that they could in future cease to work.
Link to comment
Share on other sites

If you want to avoid the thousands of loops you need to use my method.

Appropriate scripting means that you would only need one of these while clauses for any script. The set state continue would be decided by a variable or another SDF.

As for inputting a number way too large for a valid SDF, it has an inherent crash-blocking device: if an input is not 0 thru 9 no new number is recorded for a given flag.

Link to comment
Share on other sites

@ Ishad Nha:

I did look at your script again and it certainly works, but there are three things that bother me.

 

-It asks a response once per digit. While that certainly makes decoding easy, it’s tedious for the user. What’s worse, you don’t specify which digit should be entered next. At some point during the input, the user will think ‘did I input three or four numbers?’ Perhaps you can make variable text in the get_text_response call.

 

-It’s very spread out. Now, this is a personal stylistic preference, so feel free to ignore it, but I always like my code to be compact. Instead of manually checking 0-9, I’d nest another ‘while’ with an append number call.

 

-There is no check about whether a number was inputted or not. If the user just presses ‘ok’, that digit slot will count as ‘0’ (Best case scenario. You could get an UE error instead…). You could initialize the flags to, say, 255 then check them at the end.

 

 

@ Niemand: Hazarding a guess; Does the dark magic involve drawing parallels between Averrnum and Exile? I’m certain there are a lot of similarities.

Link to comment
Share on other sites

Yes, these are problems okay. Be thankful that anything is possible, given the way that Avernumscript is way too simplified.

 

"-It asks a response once per digit."

Those are the breaks. I can't think of any other method that works for large numbers without large numbers of loops.

 

"Perhaps you can make variable text in the get_text_response call."

Good idea, if Ascript permits it, it is possible.

 

"I’d nest another ‘while’ with an append number call."

I will look into this.

 

"-There is no check about whether a number was inputted or not."

In my post #195591 - Jan 20, 2010 7:51 PM, see state 79, it tells you what number you actually inputted. This will enable you to spot an error immediately.

Link to comment
Share on other sites

The problem with state 78 is that it reads an illegal input as ‘0’. Also, should that happen, the user has to input everything from the top.

 

Try this instead:

Code:
variables;short i,j,l;string response, digit;beginstate 15;i = 1;clear_buffer();while (i < 5) {   append_string("Give me digit ");   append_number(i);   get_buffer_text(response);   get_text_response(response);   j = 0;   set_flag(150,i,255);   while (j < 10) {      clear_buffer();      append_number(j);      get_buffer_text(digit);      check_text_response_match(digit);	if (got_text_match() == 1)	   set_flag(150,i,j);                     j = j + 1;   }   clear_buffer();   if (get_flag(150,i) == 255)      append_string("Wrong! ");   else       i = i + 1;	}l = 1000 * get_flag(150,1) + 100 * get_flag(150,2) + 10 * get_flag(150,3) + get_flag(150,4);print_big_str("The value for l is ",l,".");break; 

 

It uses your individual input, is compact and checks for legality for every digit, prompting for instant correction. Note however that it only goes up to 9999. If the ‘l’ variable goes beyond 32767, it bugs out, because avernumscript allows only up to signed shorts. With minor tweaking, you could turn it into unsigned short and go up to 65535, but for anything more, you’d need more fancy coding.

 

Link to comment
Share on other sites

If you input an invalid number, it will retain the previous value of the relevant SDF, which is initially 0. At least it won't crash the game. That is why you need state 79 to check that you entered all digits correctly. Yes, if you get it wrong you must start all over again.

Shortcut: pressing Enter retains the previous value that was entered. So if you input four digits and mess up only one, you can press Enter three times. Second time around you only need to enter one actual digit.

In C you just enter the number you want, you don't need to worry about any of these methods, Ascript is way too simplified.

Link to comment
Share on other sites

  • 8 months later...

Making the get_text_response text variable:

This is simple enough, just have one text per value of i, I have checked this, it works in practice. It is useful because you may lose track of which digit you now entering and you may have different allowable values for each digit. For example in a list of town numbers, the allowable first digit will be 0 or 1 only.

beginstate 134;

i = 5;

while (i < 8) {

 

if (i ==5)

get_text_response("Town number digit 1: 0 - 1?");

if (i ==6)

get_text_response("Town number digit 2: 0 - 9?");

if (i ==7)

get_text_response("Town number digit 3: 0 - 9?");

 

check_text_response_match("0");

if (got_text_match()){

set_flag(200,i,0);

}

Click to reveal..
check_text_response_match("1");

if (got_text_match()){

set_flag(200,i,1);

}

check_text_response_match("2");

if (got_text_match()){

set_flag(200,i,2);

}

check_text_response_match("3");

if (got_text_match()){

set_flag(200,i,3);

}

check_text_response_match("4");

if (got_text_match()){

set_flag(200,i,4);

}

check_text_response_match("5");

if (got_text_match()){

set_flag(200,i,5);

}

check_text_response_match("6");

if (got_text_match()){

set_flag(200,i,6);

}

check_text_response_match("7");

if (got_text_match()){

set_flag(200,i,7);

}

check_text_response_match("8");

if (got_text_match()){

set_flag(200,i,8);

}

check_text_response_match("9");

if (got_text_match()){

set_flag(200,i,9);

}

i = i + 1;

}

a = 100 * get_flag(200,5) + 10 * get_flag(200,6) + get_flag(200,7);

set_flag(100,0,a);

revive_party();

set_state_continue(24);

break;

 

Link to comment
Share on other sites

Yes, it's a tradeoff.

 

Your method looks like it accepts N digit numbers using N requests to the user for single digits and ~ 39*N nodes. It's relatively lightweight in terms of code use but much more of a bother to the user.

 

My method allows the user to type out the entire number, once, and uses, in the good case (Mac OS), ~ 14*n nodes [1], where n is the number of characters entered by the user, and in the bad case (Windows [2]), uses ~ 14*10^N nodes to read N digit numbers. This is less annoying from the user, and if my figuring is correct, cheaper computationally in the good case, but far worse computationally in the bad case. Since the method is already a hybrid, if one were really hurting for nodes, your method could be substituted for my Windows fall-back code, with the helpful side-effect of punishing users of inferior computers tongue.

 

[1]: The reader may note that 14*6 is significantly smaller than the upper bound of 500 I claim in the notes for the code on the Blades Forge. I know I chose that bound to be an overestimate, but I'm not sure why it's so much higher than what I get now. Maybe I made a mistake either then or now, but I don't know which.

[2]: For technical reasons, that I won't go into here, it appears to be impossible to apply the clever trick which makes the Macintosh code so efficient when running on Windows.

Link to comment
Share on other sites

You may need one method for the Windows version of a scenario and your method for the Mac version.

One digit at a time is annoying but this is Avernumscript.

CM has a Mac perspective on this new method by Niemand. By contrast I have a Windows perspective. It is an interesting technical tour de force by someone who knows what he is doing but it won't work well for Windows.

Link to comment
Share on other sites

Even the method using a loop and the string buffer is in general better than yours; I don't consider inconvenience to the user to be an acceptable trade-off. If I recall, each iteration of the loop uses less than 10 nodes, and the loop runs n times where n is the number entered by the user, so it's not that bad cost-wise anyway (at least for small enough numbers).

Link to comment
Share on other sites

Originally Posted By: Ishad Nha
How big are small enough numbers?
I dunno, I haven't done the calculation. Perhaps around 256? Maybe more.

Originally Posted By: Ishad Nha
The real problem here is that Avernumscript can't handle a simple "cin".
scanf/cin is not what it needs, since those rely on the existence of a console that Avernum doesn't have. What it needs is a function to convert the input from the text box into a number and return it.
Link to comment
Share on other sites

Originally Posted By: Celtic Minstrel
Originally Posted By: Ishad Nha
How big are small enough numbers?
I dunno, I haven't done the calculation. Perhaps around 256? Maybe more.


Originally Posted By: Niemand
. . . in the bad case (Windows [2]), uses ~ 14*10^N nodes to read N digit numbers

It uses ~7 nodes per number checked (plus a small constant number), so if you only wanted to accept numbers in [0,99], it would take a maximum of ~700 nodes, but fewer if it found a match. Ishad Nha's method would take around 80 nodes, I think, but would use two prompts and require the user to use leading zeros for one digit numbers. (In the Macintosh case, my method would be able to handle this range of numbers in a maximum of less than 50 nodes, if my quick mental math is correct.) The method of brute-force checking numbers sequentially will only be faster than the digit by digit method if there are ~5 or fewer numbers in the acceptable range. Finally, Ishad Nha's method cannot (as written above) handle negative numbers.

I don't think that we're covering any new ideas at this point. Reading the character buffer is always the fastest method. Otherwise, reading digit by digit is computationally cheap but annoying for the user, whereas checking numbers sequentially is potentially computationally expensive but convenient for the user. I would conclude: Use the latter if numbers are small and you know you have nodes to burn; use the former if you find you have to.
Link to comment
Share on other sites

Negative numbers could be handled easily enough, the solution is:

check_text_response_match("-");

Then set a variable, say b, to a value of -1. Its default value is 1.

Collect all the digits, multiply them by the relevant powers of 10. Add all the terms together, then multiply by b.

 

You can make my method a bit easier to use.

It is simple to have a printout of the existing values in the text area.

Ditto you can also print the final result there.

I have already shown how it is possible to have customized get text response messages for each value of i in the iteration.

It seems that the short numbers are signed and 2 byte, hence are found in the range +/- 31,767. You would be inputting a maximum of five digits and maybe one minus sign.

Method works by saving numbers as SDFs, if the previous value of a SDF is acceptable you can just press Enter.

(BoA is the only game I can think of where this is an issue). Even BoA can regularly handle numerical input in one area, the Character Editor.

 

 

Link to comment
Share on other sites

  • 4 months later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...