DragonBoots said:
Look again.
1.) Bit-wise TSC processors are good for things that will remain static or for very large lists (more than ~16 items) as it consumes fewer flags. However, making it able to increment and decrement can be tricky, as you have to check (for either) what your current value is before incrementing or decrementing, which expands the script tremendously.
Um, what? Why would you need to know the current value of the flagset before incrementing? In pseudo-TSC, assuming the number occupies flags 0001, 0002, 0003, and 0004 (in order, so that the value 1 is when flag 4 is set and the other three are unset):
Code:
#0001
<[color=#7F7F7F]FLJ[/COLOR][color=#7F7F7F]0004[/COLOR]:[color=#7F7F7F]0002[/COLOR]<[color=#7F7F7F]FL+[/COLOR][color=#7F7F7F]0004[/COLOR]<[color=#7F7F7F]END[/COLOR]
#0002
<[color=#7F7F7F]FL-[/COLOR][color=#7F7F7F]0004[/COLOR]<[color=#7F7F7F]FLJ[/COLOR][color=#7F7F7F]0003[/COLOR]:[color=#7F7F7F]0003[/COLOR]<[color=#7F7F7F]FL+[/COLOR][color=#7F7F7F]0003[/COLOR]<[color=#7F7F7F]END[/COLOR]
#0003
<[color=#7F7F7F]FL-[/COLOR][color=#7F7F7F]0003[/COLOR]<[color=#7F7F7F]FLJ[/COLOR][color=#7F7F7F]0002[/COLOR]:[color=#7F7F7F]0004[/COLOR]<[color=#7F7F7F]FL+[/COLOR][color=#7F7F7F]0002[/COLOR]<[color=#7F7F7F]END[/COLOR]
#0004
<[color=#7F7F7F]FL-[/COLOR][color=#7F7F7F]0002[/COLOR]<[color=#7F7F7F]FLJ[/COLOR][color=#7F7F7F]0001[/COLOR]:[color=#7F7F7F]0005[/COLOR]<[color=#7F7F7F]FL+[/COLOR][color=#7F7F7F]0001[/COLOR]<[color=#7F7F7F]END[/COLOR]
#0005
<[color=#7F7F7F]END[/COLOR] [color=#7F7F7F]// The execution of this event signals that the integer has
// overflowed from max (15 in this example) to 0.[/COLOR]
Note: The assumption in this code is that FLJ/FNJ takes the flag as the first argument. It also assumes unsigned integers, because signed integers complicate things a bit. I think.
And for decrement:
Code:
#0001
<[color=#7F7F7F]FNJ[/COLOR][color=#7F7F7F]0004[/COLOR]:[color=#7F7F7F]0002[/COLOR]<[color=#7F7F7F]FL-[/COLOR][color=#7F7F7F]0004[/COLOR]<[color=#7F7F7F]END[/COLOR]
#0002
<[color=#7F7F7F]FL+[/COLOR][color=#7F7F7F]0004[/COLOR]<[color=#7F7F7F]FNJ[/COLOR][color=#7F7F7F]0003[/COLOR]:[color=#7F7F7F]0003[/COLOR]<[color=#7F7F7F]FL-[/COLOR][color=#7F7F7F]0003[/COLOR]<[color=#7F7F7F]END[/COLOR]
#0003
<[color=#7F7F7F]FL+[/COLOR][color=#7F7F7F]0003[/COLOR]<[color=#7F7F7F]FNJ[/COLOR][color=#7F7F7F]0002[/COLOR]:[color=#7F7F7F]0004[/COLOR]<[color=#7F7F7F]FL-[/COLOR][color=#7F7F7F]0002[/COLOR]<[color=#7F7F7F]END[/COLOR]
#0004
<[color=#7F7F7F]FL+[/COLOR][color=#7F7F7F]0002[/COLOR]<[color=#7F7F7F]FNJ[/COLOR][color=#7F7F7F]0001[/COLOR]:[color=#7F7F7F]0005[/COLOR]<[color=#7F7F7F]FL-[/COLOR][color=#7F7F7F]0001[/COLOR]<[color=#7F7F7F]END[/COLOR]
#0005
<[color=#7F7F7F]END[/COLOR] [color=#7F7F7F]// The execution of this event signals that you've
// overflowed from 0 to max.[/COLOR]
DragonBoots said:
2.) Flag-Per-Item counters are best for short lists of things that will be frequently changed. Incrementing or decrementing the stored value is basically as simple as a string of flag checks that lead to different events to decrement or increment appropriately (which could appear as <FL-XXXX<FL-XXXX<FL-XXXX<FL+YYYY if it's tracking something like a quantity - Since only the flag for the "current" quantity needs to be set).
Not really sure how incrementing works here...?
andwhyisit said:
<BIJXXXX:YYYY:ZZZZ - Add the values from Z and range X:Y together and run the resulting event number.
...this command does not make sense.
DragonBoots said:
Probably work best as <BIJWWWW:XXXX:YYYY:ZZZZ
Makes more sense this way. However, it might be easier to specify a flag block (byte, or a flag that's a multiple of 8) rather than a flag, and omit the endpoint (for any math function). Working with numbers spanning across multiple bytes could get tricky (assuming it's not 2 or 4 full bytes).
DragonBoots said:
<VARXXXX - Stores variable X. 0000 should clear. Should only accept standard numerical values.
<VA+ - Increments existing variable X
<VA- - Decrements existing variable X
Variables would indeed be nice. Your second suggestion (with a Y) would be better though.
DragonBoots said:
<TRA0100:0050:0020:V001 for the first variable (the "V" informing it to check the slot).
This could work, of course; it'd be slightly easier to instead have a <VLDXXXX command that substitutes the value of the specified variable into the first 0000 parameter of the next command, but that's also a significant loss of flexibility.
Noxid said:
"There isn't really any RAM to make variables in" as far as I know.. Well, unless you kill the Old Map data. Or that smidgen at 4bba00 lace told me bout'.
Um, or you could allocate more. Just make sure to deallocate it when you're done, or you'll have major memory leaks. Assuming Cave Story is linked with the full C standard library, all you would have to do is call malloc to allocate and free to deallocate. (You'd need to find the address of these functions). Both functions take one argument; malloc takes as an argument the number of bytes to allocate and returns the address of the new memory; later, when you're done with it, you would pass that address to free as its argument.
I'm not entirely sure how to call functions in assembly, but it would be something along the lines of "push arg; call func;".
Noxid said:
If the variable had to save, then I'd endorse the use of some flag data unless you were feeling like hacking the save feature which I've not actually looked into myself so I can't vouch for the feasability.
Again, this would be done by calling a library function (most likely fwrite, but it's also possible that Pixel called the Windows file handling functions directly). Just find the code where saving is done, and duplicate the logic.
Noxid said:
Though you would still need a command to store the variable to the flag data (when saving data), and another to retrieve the flag data and store it to a variable (when loading data).
BINXXXX:YYYY:VZZZ - Store var z into range x:y.
BVRXXXX:YYYY:ZZZZ - Load data from range x:y and store in z.
I don't see what's wrong with storing variables in a new section instead.
andwhyisit said:
How much space in ram do you have to burn?
Theoretically, unlimited.
DragonBoots said:
I say impose a reasonable limit on variables, say 256 in total (V000:V255)?
This is reasonable. You could avoid having a limit altogether through judicious use of malloc, but that's probably more work than you'd want to deal with when programming in assembly.
DragonBoots said:
Though I'm sure an array of some description could store such data more effeciently.
Not especially; however, using an array makes it easy to find where in memory your variable is. To use an array, you need to allocate the
entire array all at once. So, if you want an array of 256 2-byte variables, the argument to malloc would be 512.
DragonBoots said:
A basic counter for 20 items. Only worries if you have none of have full. Some form of variable-based return could be handy here, to allow it to return if it succeeds (Eg. Flag 120 or 100 AREN'T set) to a previous event (or variable subsequent one), allowing to mimic a stack-like arrangement. THIS IS WHAT I DREAM OF.
What do you mean by a "stack-like" arrangement?
With just variables and no call stack or any such conveniences, the best way to do return values would probably be to use a reserved variable. Example:
Code:
#0001
<[color=#7F7F7F]VAR[/COLOR][color=#7F7F7F]0000[/COLOR]:[color=#7F7F7F]0002[/COLOR]<[color=#7F7F7F]JMP[/COLOR][color=#7F7F7F]0003[/COLOR] [color=#7F7F7F]// This JMP calls the function[/COLOR]
#0002
<[color=#7F7F7F]MSG[/COLOR]The function returned <[color=#7F7F7F]PRN[/COLOR][color=#7F7F7F]0001[/COLOR]!<[color=#7F7F7F]END[/COLOR]
[color=#7F7F7F]// This event runs after the function call.[/COLOR]
#0003
<[color=#7F7F7F]VAR[/COLOR][color=#7F7F7F]0001[/COLOR]:[color=#7F7F7F]0120[/COLOR]<[color=#7F7F7F]JMP[/COLOR][color=#7F7F7F]V000[/COLOR] [color=#7F7F7F]// And this event is the function[/COLOR]
Note that a function could span multiple events, of course.
DragonBoots said:
Also, on a final note: Perhaps allow each variable to have a command form.
This is a nice idea.
DragonBoots said:
<MSGHello world, I am <001 years old!<NOD<CLR<VA+0001:0001<END
Would initially display "Hello world, I am 0 years old!" and each subsequent time until it/the game is reset would display the number as 1 greater.
Not so. Unless you actually set the variable to 0 at some earlier time, you cannot predict what the initial value of a variable will be.
Though, my suggestion would be to zero the entire variable area of RAM using memset. I forget how that function works though.
andwhyisit said:
Actually scratch that, pseudo-random number generation in assembly sounds complicated.
Fortunately, it has been done for you. Call the rand library function.
...actually, I'm pretty sure Noxid said that already.
andwhyisit said:
It is better to have a single command (<PRNXXXX, where x is the variable number) to print the variable instead rather than make 256 new commands.
Actually, you wouldn't have to make 256 new commands. You could make just one. However, instead of comparing each of the three characters in turn to a hard-coded value, you would check the return value of the isdigit function, which takes a single argument: the character to check. If it returns 1 (non-zero, technically) for all three characters, you know this is a print variable command, and you backtrack to get the number of the variable.
That said, the <PRN command would probably be less work.
Noxid said:
PUSH max
PUSH min
Call 40F350
Add ESP, 8
Returns a number between min and max to EAX
...wait, what? Which function are you calling here? That is, what would be the C name of the function? The rand() function I know does not take arguments.
Noxid said:
<001 as a command would be unreasonable based on how the parser works by comparing each character in sequence to a hardcoded value you'd need to extend the parser for EVERY possiblity.
Oh but I see Andwhy picked up on that.
It would not be unreasonable. Just because the parser works like that does not mean you can make an exception for this command.
andwhyisit said:
<MASXXXX - Store var x into 4-byte math variable for math operations.
<MADXXXX - Divide math variable by x, store in math variable.
<MDRXXXX - Divide x by math variable, store in math variable.
<MAMXXXX - Multiply math variable by x, store in math variable.
<MAAXXXX - Add math variable to x, store in math variable.
<MSUXXXX - Subtract math variable by x, store in math variable.
<MSRXXXX - Subtract x by math variable, store in math variable.
<MPOXXXX - Math variable to the power of x, store in math variable.
<MPRXXXX - X to the power of math variable, store in math variable.
<MMOXXXX - Modulus of math variable and x, store in math variable.
<MMRXXXX - Modulus of x and math variable, store in math variable.
<MAEXXXX:YYYY - Store math variable into var x, using rounding method y
...whoa. Why do it this way? It sounds like your plan is to have a special floating-point "register" for math operations. You should know that this is a bad idea if you really only care about integer math; floating-point numbers cannot represent decimal portions accurately unless they are multiples of a power of 1/2. It's much like how you can't represent 1/3 accurately in the decimal system, except that in binary there are a lot less fractions you can accurately represent.
andwhyisit said:
Rounding methods:
0 = normal rounding with negative values becoming 0 (-1 is now 0).
1 = round down with negative values becoming 0.
2 = round up with negative values becoming 0.
3 = normal rounding with negative values becoming positive (-1 is now 1).
4 = round down with negative values becoming positive.
5 = round up with negative values becoming positive.
6 = save digits after the decimal point as int (0000000.XXX).
7 = save as 1 for a positive number and 0 for a negative one.
8 = save the first four digits to the left of the decimal point (000XXXX.000).
9 = save the last three digits to the left of the decimal point (XXX0000.000).
Well, this is a pretty odd collection of rounding methods. Method 7 is not rounding at all (it's the signum or sgn function). Also, define "normal rounding", because there's no one normal method of rounding.
For reference, standard rounding methods are usually something like the following (assuming we have signed integers, though): TO_INFINITY, TO_ZERO, TO_NEG_INFINITY, TO_NEAREST, and the last one which I don't know the name of. TO_NEAREST would be the common "check the first digit after the decimal, round up if >5 else round down). The nameless one is similar, except if the digit is 5 and the next digit after the decimal point is even (or odd, I forget which) you round down instead of up. Examples:
Code:
NUMBER ZERO INF NEG_INF NEAREST OTHER
3.45 3 4 3 3 3
2.43 2 3 2 2 2
5.53 5 6 5 6 5
5.56 5 6 5 6 6
3.79 3 4 3 4 4
-3.45 -3 -3 -4 -3 -3
-2.43 -2 -2 -3 -2 -2
-5.53 -5 -5 -6 -6 -5
-5.56 -5 -5 -6 -6 -6
-3.79 -3 -3 -4 -4 -4
I'm thinking that if you really wanted such a command set, it would be helpful to make the rounding type a decimal "bitfield". That is, each decimal digit has a separate meaning.
So, something like this:
<MAEXXXX:ABCD
Values for D (rounding method):
0 - Towards zero (truncate)
1 - Towards infinity
2 - Towards negative infinity
3 - To nearest
4 - To nearest with statistical correction
5 - Retain fraction only (X - floor(X) if positive, X - ceil(X) if negative)
Values for C (sign behaviour):
0 - Keep sign (might not be useful)
1 - Apply abs() function (negative numbers become positive)
2 - Apply sgn() function (1 if positive else 0; though, technically, this is not how sgn() works; it returns -1 if negative and 0 only if 0)