Wrapping, capping, toggling, and slicing, oh my! - Printable Version +- QB64 Phoenix Edition (https://staging.qb64phoenix.com) +-- Forum: QB64 Rising (https://staging.qb64phoenix.com/forumdisplay.php?fid=1) +--- Forum: Expanding Horizons (Libraries) (https://staging.qb64phoenix.com/forumdisplay.php?fid=21) +---- Forum: One Hit Wonders (https://staging.qb64phoenix.com/forumdisplay.php?fid=24) +---- Thread: Wrapping, capping, toggling, and slicing, oh my! (/showthread.php?tid=1226) |
Wrapping, capping, toggling, and slicing, oh my! - johannhowitzer - 12-03-2022 Who knows, maybe everyone out there is already using these things in some form, but I use them EVERYWHERE and maybe someone will enjoy them! They're just simple and common ways to manipulate values, but they will replace multiple lines of code and make said code much easier to understand at a glance. Code: (Select All) function wrap(n, l1, h1) ' n is adjusted back within lower(l) and upper(h) bounds similar to mod operator This first function wraps a value between two values. You pass n, and if it's beyond one of the bounds, it wraps back around. So say you have five menu options, and the user moves the cursor to position 6, this will wrap it back around to 1. Going to 0 will also wrap around to 5. It doesn't matter how far outside you go, and negatives are treated properly, so -256 in this example will get wrapped correctly to 4. The bounds can be in any order, and n is usually going to be a simple addition or subtraction. So in the case of the above menu example, [wrap(cursor + 1, 1, 5)] will move the cursor, with the check to wrap around. And in most menu navigation scenarios, wrapping is super nice for the user. So you can write the following: if [input] = [up arrow] then cursor = wrap(cursor - 1, 1, [max]) if [input] = [down arrow] then cursor = wrap(cursor + 1, 1, [max]) You could also use this to wrap coordinates, so the player can leave the right side of your stage and emerge on the left, like the Pac-Man tunnel, or create a seemingly infinitely scrolling stage, like Asteroids, which really just repeats on itself. For the latter example, you would wrap the coordinates of objects whenever they travel beyond the stage, and also wrap the coordinates to within a range around the player's position, when using them to draw the screen. The result will appear seamless, and it won't matter how fast things travel each frame. Code: (Select All) function wrap_a(a) ' angle a is adjusted back within 0 and 2pi, noninclusive of 2pi This does exactly the same thing, but for angles. Put any angle value in this function, and it will simplify it to a positive angle less than a full circle. Very useful when angle values can get adjusted over and over. Just put the function around any angle changes. (Note that the little atn1() function is just a shorthand thing, I got tired of typing n * atn(1) in a ton of places. It generates an angle in radians, where the value passed in is an eighth of a circle or 45 degrees, so atn1(8) = 2 * pi radians, or 360 degrees.) Code: (Select All) function plus_limit(n, p, l) ' p is added to n, but can't go past l in the direction of travel This saves a lot of space capping values, and is direction-dependent. If p is positive, n can't go past l upward; if p is negative, n can't go past l downward. I use this for many things, like decaying values without going below zero, capping healing at maximum health, trapping position coordinates at the edges of the screen, the uses are endless. The syntax is very clean, all packed up in one line, it replaces stuff like this: health = health + 100 if health > max_health then health = max_health with this: health = plus_limit(health, 100, max_health) The directional dependency of this is more useful than you might think at first glance, and is a feature that simple floor and ceiling functions don't have. For example, you can use sgn() comparison to move something toward a goal value, and stop it if it reaches the goal, but not snap to the goal if moving away from it. Then you might use something like this: x = plus_limit(x, sgn(goal - x) * speed, goal) This can be very useful in having one value that changes instantly, and another that constantly follows it. In my current game project, one way I use this is to give visual feedback about damage that was just taken. If the player gets hit, the health is instantly lowered, so the green health bar gets shorter. But there's a hidden second red bar behind it, and that red bar's value follows the health value. If you gain health, this does nothing, since the red bar will be shorter, but when you LOSE health, the player will see part of the health bar become red, and instantly start shrinking, until all that's left is the green part. Do note that because of the dependency, if for example you are subtracting, this function won't catch a value that was already higher than it should be. So be careful. Code: (Select All) function toggle(v, p, q) This one just toggles between the given values. However, it will not do anything if the variable passed is not one of these two values. Use like v = toggle(v, value1, value2). Replaces the messier [if v = value1 then v = value2 else v = value1]. And since it's a function, you can use it very compactly and dynamically, rather than directly manipulating variables. Let's say you have a 1v1 RPG combat scenario, and one of the characters decided to attack. You've calculated the damage already. So instead of: if turn = char1 then target = char2 else target = char1 call damage_char(target, damage) You can write: call damage_char(toggle(turn, char1, char2), damage) Think of it like the binary NOT operator, except this works between any two values you want. Code: (Select All) function before$(t$, c$) These functions are used for slicing strings, which has been useful in developing my own scripting, among other things. before$ will return everything in t$ before the first instance it finds of c$, while after$ will return everything after it. If c$ is not found, it will simply return t$ unchanged. An interesting application of this is to return more than one value from a function. For example, if you want a function to return both x and y coordinates, you can make the function's return data type a string, pack the two values up with a comma between like "100, 50", then use before and after functions with val() to pull the values apart outside the function. You can also easily do this for three-dimensional coordinates. And it need not be limited to coordinates, you could also use a separator character to return multiple strings packed into one string, like "Alice|Timothy|Eric". between$, of course, just cleans up the syntax when using before$ and after$ together, which often happens in my code. This: result$ = before$(after$(t$, "["), "]") Becomes this: result$ = between$(t$, "[", "]") RE: Wrapping, capping, toggling, and slicing, oh my! - mnrvovrfc - 12-03-2022 (Round of applause) The "plus_limit" is something that appeared for me a lot, I mean a lot, in my source code in any language. The "toggle" I call "tert" in my version of Lua, could be written even simpler: Code: (Select All) function toggle&& (valu&&, one&&, two&&) It would be even better if it could support any data type. I have one of my own that some of you might have already seen elsewhere in my source code: Code: (Select All) FUNCTION LeftLen$ (tx$, numchar&) It takes "numchar&" bytes away from the end of the string. Quick example, cannot think well right now due to lack of sleep: Code: (Select All) a$ = "Pete" I have also written a "SEG1$()" which acts more like "string.sub()" in Lua, ie. give the starting and ending inclusive points to get a fragment of a string, and supports zero and negative values. It had to be "SEG1$()" not "SEG$()" because of "DEF SEG". Code: (Select All) ''this works a bit differently from "string.sub()" in Lua Sorry because it's not indented, blame the forum liking to eat spaces at the front of lines sometimes. RE: Wrapping, capping, toggling, and slicing, oh my! - johannhowitzer - 12-03-2022 Interesting stuff. Regarding the last string-related functions, I have plans in the works to do a short tutorial video about implementing a simple scripted-behavior language in your own program, which I am now using for my big game project, it's paved the way to much cleaner code and lots of flexibility, and makes extensive use of before$() and after$(), as I mentioned. Hopefully will get around to doing that soon. RE: Wrapping, capping, toggling, and slicing, oh my! - grymmjack - 12-28-2022 These are great! I'll share mine in another thread. RE: Wrapping, capping, toggling, and slicing, oh my! - mdijkens - 12-28-2022 (12-03-2022, 11:40 AM)mnrvovrfc Wrote: The "toggle" I call "tert" in my version of Lua, could be written even simpler: or even simpler: Code: (Select All) Function toggle&& (valu&&, one&&, two&&) |