| Both sides previous revisionPrevious revisionNext revision | Previous revision |
| cw4:4rpl_tools [2025/06/23 18:39] – [Request Viable Move- or Build-Cell] kalli | cw4:4rpl_tools [2025/08/29 21:04] (current) – [Pseudo RNG based on Linear congruential generator (LCG)] kalli |
|---|
| |
| |
| ===== Request Viable Move- or Build-Cell ===== | ===== Request Viable Build- or Move-Cell ===== |
| |
| <hidden click here for source code> | <hidden click here for source code> |
| |
| This script allows for other scripts to SEND A MESSAGE TO REQUEST A VIABLE MOVE- OR BUILD-CELL. | This script allows for other scripts to SEND A MESSAGE TO REQUEST A VIABLE BUILD- OR MOVE-CELL. |
| |
| | The script is a compromise between high performance and high fidelity. The reason that it's setup to run as a separate script and not as a function, is so that it can take move/build requests from multiple sources into account: by default it won't recheck cells that were recently rejected + new searches will continue where recent previous queries stopped after finding a solution. A very high performance version exists for 3x3 units in my SQUADS map. |
| The script is a compromise between high performance and high fidelity. The reason that it's setup to run as a separate script and not as a function, is so that it can take move/build requests from multiplie sources into account: it won't recheck cells that were recently rejected + new searches will continue where recent previous queries stopped after finding a solution. A very high performance version exists for 3x3 units in my SQUADS map. | |
| |
| |
| |
| |
| If the units are special custom units (build in void/anywhere or build as miner/nullifier, then their GUID needs to be added to the relevant lists inside the script. | Use example for building miners: |
| |
| |
| INPUT: | <code 4rpl example.4rpl>500 0 do |
| <code 4rpl MultiObjective.4rpl>"requestWxWviableCell" <-UID <-cellV2 <-searchRange <-maxCreeperDepth <-noMesh <-unitWidth <-ignoreCooldown 7 listN sendmsg</code> | "requestWxWviableCell" 0 5 3 128 128 v2 256 0 0 0 0 1 0 1 1 0 14 listN sendmsg # Length > width = x direction. |
| OUTPUT: 2 global variables, see example: | <-*WxWviableCellFound if |
| <code 4rpl MultiObjective.4rpl><-*WxWviableCellFound if | "miner" <-*WxWviableCell ev2 0 createunitonterrain dup 1 SetUnitOccupiesLand 999 constructunit |
| <-UID <-*WxWviableCell ev2 setunitmovecell | # Unlike existing units that move, newly build units do not instantly occupy the cells that they were created on, so we force a refresh of that with SetUnitOccupiesLand. |
| endif</code> | endif |
| | loop |
| | |
| | 500 0 do |
| | "requestWxWviableCell" 0 3 5 128 128 v2 256 0 0 0 0 1 0 1 1 0 14 listN sendmsg # Length > width = z direction. With Z-direction we also need to change the orientation below. |
| | <-*WxWviableCellFound if |
| | "miner" <-*WxWviableCell ev2 0 createunitonterrain dup dup 1 setunitorientation 1 SetUnitOccupiesLand 999 constructunit |
| | endif |
| | loop</code> |
| |
| INPUT variables explained: | INPUT variables explained: |
| <-UID | |
| <-cellV2: The cell where your requesting script wants to build/move the unit. | <-moveUID: can be left 0, but it's needed if you want to allow a unit to check if it's current location is viable. |
| <-searchRange: the cell distance around the requested cell location, where the function will look for a suitable build/move cell. | |
| | <-unitLength: footprint length in X direction. |
| | |
| | <-unitWidth: footprint length in Z direction. |
| | |
| | <-cellV2: V2 vector. The cell where your requesting script wants to build/move the unit. |
| | |
| | <-searchRange: the cell distance around the requested cell location, where the function will look for a viable build/move cell. |
| <-maxCreeperDepth: this will check the average creeper depth in a circle with range 4. | <-maxCreeperDepth: this will check the average creeper depth in a circle with range 4. |
| | |
| <-noMesh: set to 1 to not allow solutions with ACTIVE mesh. | <-noMesh: set to 1 to not allow solutions with ACTIVE mesh. |
| <-unitWidth: the footprint witdth=length of the unit. This assumes square units. | |
| <-ignoreCooldown: set to 1 to ignore the computation optimizations. | |
| |
| <code 4rpl MultiObjective.4rpl> | <-likePlatform: the unit can be placed in the void. |
| # --WxW-FindViableCell-NRP-Pre-- | |
| | <-likeBeacon: the unit can be placed in the void and on uneven terrain. |
| | |
| | <-likeMiner: the unit can only be placed on resource special terrain that covers atleast 75% of the unit footprint. |
| | |
| | <-likeNullifier: the unit has to be placed within nullification range of an enemy creeper. The creeper enemey has to be known to a list inside the function script. |
| | |
| | <-ignoreCooldown: set to 1 to ignore time based computation optimizations. Best left at 0 when the function is called by an AI script (more optimized) and set to 1 when it's initiated by the player (feels more responsive). |
| | |
| | <-noSkip: set to 1 for max placement coverage, but slower computations. If left at 0, then the script will not check cells that are likely to not have a proper solution, but it will also mistakenly not check some cells that do have a proper solution. For spamming 3x3 units, "1" tends to be 40% slower, while only placing about 10% more units. |
| | |
| | <-resetSearch: if set to 1, then all previous search history will be forgotten. |
| | |
| | |
| | |
| | <code 4rpl LxW-FindViableCell-NRP-Pre.4rpl> |
| | # --LxW-FindViableCell-NRP-Pre-- |
| |
| # INPUTS: "requestWxWviableCell" <-UID <-cellV2 <-searchRange <-maxCreeperDepth <-noMesh <-unitWidth <-ignoreCooldown 7 listN sendmsg | # INPUTS: "requestWxWviableCell" <-moveUID <-unitLength <-unitWidth <-cellV2 <-searchRange <-maxCreeperDepth <-noMesh <-likePlatform <-likeBeacon <-likeMiner <-likeNullifier <-ignoreCooldown <-noSkip <-resetSearch 14 listN |
| # OUTPUT: global varialbles named <-*WxWviableCellFound <-*WxWviableCell | # OUTPUT: global varialbles named <-*WxWviableCellFound <-*WxWviableCell |
| |
| # BASED ON SNAPPING TOOL CODE, but without the Indicator unit or the Mouse commands, and only for SQUARE units. | # BASED ON SNAPPING TOOL CODE, but without the Indicator unit or the Mouse commands, and only for SQUARE units. |
| |
| $radiusLandingSpot:4 # The creeper threshhold will be checked as an average over more than the unit landing location. | $radiusLandingSpot:4 # INTEGER. The creeper threshhold will be checked as an average over more than the unit landing location. |
| $cellsWithinLandingRange:69 # The creeper threshhold will be checked as an average over more than the unit landing location. | $cellsWithinLandingRange:69 # INTEGER, see previous radius. The creeper threshhold will be checked as an average over more than the unit landing location. |
| |
| $$defaultSearchRange:20 # If no searchRange is send in the message, this is the range that will be used. | $$defaultSearchRange:20 # INTEGER. The floodfill distance. If no searchRange is send in the message, this is the range that will be used. |
| |
| $$contSlot:8 # Units cannot be placed on this terrain slot. 8 is the default contaminant slot. | $groupDist:7 # Integer or float distance. Requests that are closer together than this distance, will be grouped together, so that later searches can continue on from previous searches. |
| $$resoSlot:1 # Miners can only be placed on resource terrain. 1 is the default resource slot. | $blockCellTimer:90 # INTEGER. Amount of frames under which queries will be grouped together & searches around cells that were found to be blocked, will not be done again. |
| $$meshSlot:6 # Units that can be placed in the void, cannot be placed on mesh inside the void. 6 is the default mesh slot. | $clearCellTimer:270 # INTEGER. Amount of frames after which the blocked cells will be forgotten. In between these 2 values, searches will only be done again if there are less units around than when the cell was last found to be blocked. |
| | |
| | $$contSlot:8 # VALUE EDITOR SETTING. Units cannot be placed on this terrain slot. 8 is the default contaminant slot. |
| | $$resoSlot:1 # VALUE EDITOR SETTING. Miners can only be placed on resource terrain. 1 is the default resource slot. |
| | $$meshSlot:6 # VALUE EDITOR SETTING. Units that can be placed in the void, cannot be placed on mesh inside the void. 6 is the default mesh slot. |
| |
| $$unitBuildTypes:" " | |
| # $$footprintWidth:" " | |
| # $$footprintLength:" " | |
| $$voidUnitTypes:" Platform; platform" | |
| $$anywhereTypes:" Chronat; beacon" | |
| $$resourceTypes:" Reactor; miner" | |
| $$nullifyTypes:" Nullifier; nullifier" | |
| $$nullifyUnits:" emitter; airsaccauldron; blobnest; darktower; skimmerfactory; sporelauncher" # Enemy units that would be suppressed by building a nullifier near them. | $$nullifyUnits:" emitter; airsaccauldron; blobnest; darktower; skimmerfactory; sporelauncher" # Enemy units that would be suppressed by building a nullifier near them. |
| |
| :requestWxWviableCellFunction | :requestWxWviableCellFunction |
| <-_DATA[0] 0 setunitoccupiesland | <-_DATA[0] 0 setunitoccupiesland # With this line, an existing unit's current cell will be a possible result from the search for a viable cell. |
| <-_DATA[2] dup not if pop <-defaultSearchRange endif ->searchRange | <-_DATA ListToStack @searchWxWviableCell ->*WxWviableCellFound ->*WxWviableCell |
| <-_DATA[0] getunittype <-_DATA[1] <-_DATA[3] <-_DATA[4] <-_DATA[5] @searchWxWviableCell ->*WxWviableCellFound ->*WxWviableCell | |
| <-_DATA[6] ->ignoreCooldown | |
| <-_DATA[0] 1 setunitoccupiesland | <-_DATA[0] 1 setunitoccupiesland |
| |
| # listening channels: | # listening channels: |
| "requestWxWviableCell" "requestWxWviableCellFunction" registerformsg | "requestWxWviableCell" "requestWxWviableCellFunction" registerformsg |
| 1 ->resetSnapIndex | 1 ->resetSearch |
| | 1 ->ignoreCooldown |
| | |
| # When the loop was ran through fully without finding a resolution, remember that cell for 150 frames before trying the same cell again. | # When the loop was ran through fully without finding a resolution, remember that cell for 90 frames before trying the same cell again. |
| list ->blockedCellV2List | list ->blockedCellV2List |
| list ->blockedCellTimeList | list ->blockedCellTimeList |
| list ->nearbyUnitsCountList | list ->nearbyUnitsCountList |
| | |
| # the ONCE code from the snapping tool script | |
| # <-unitBuildTypes removewhitespace ";" split ->buildList | |
| # <-footprintWidth removewhitespace ";" split ->widthList | |
| # <-widthList 0 do <-widthList[i] 1 add 2.0 div asint ->widthList[i] loop | |
| # <-footprintLength removewhitespace ";" split ->lengthList | |
| # <-widthList 0 do <-lengthList[i] 1 add 2.0 div asint ->lengthList[i] loop | |
| <-voidUnitTypes removewhitespace ";" split ->voidList | |
| <-anywhereTypes removewhitespace ";" split ->anywhereList | |
| <-resourceTypes removewhitespace ";" split ->resourceList | |
| <-nullifyTypes removewhitespace ";" split ->nullifierList | |
| <-nullifyUnits removewhitespace ";" split ->suppressList | <-nullifyUnits removewhitespace ";" split ->suppressList |
| # unitType and prevType must be different at the start of the map. | |
| <-unitType not ->prevType | |
| # <-searchRange 1 sub dup dup mul swap add 2 mul 1 add ->floodRange | |
| |
| :searchWxWviableCell # Output: viableCell as v2 + true/false | :searchWxWviableCell # Output: viableCell as v2 + true/false |
| # See vanilla snapping tool cpack for full commments. | # See vanilla snapping tool cpack for full commments. |
| ->width | ->resetSearch |
| | ->noSkip |
| | ->ignoreCooldown |
| | ->checkN |
| | ->checkR |
| | ->placeA |
| | ->placeV |
| ->noMesh | ->noMesh |
| ->creDepth | ->creDepth |
| | ->searchRange |
| ->cell | ->cell |
| ->unitType | ->width |
| | ->length |
| <-unitType <-prevType neq if | pop # The UID has no further use. |
| # <-buildList <-unitType getlistindex ->typeIndex | |
| # <-widthList[<-typeIndex] ->rangeX | # The following switch checks if the search should start from scratch (=1), or if it should continue (=0) on from a previous search. |
| # <-lengthList[<-typeIndex] ->rangeZ | switch |
| <-resourceList <-unitType listcontains if 1 else 0 endif ->checkR | <-resetSearch case 1 endcase |
| <-nullifierList <-unitType listcontains if 1 else 0 endif ->checkN | <-length <-prevLength neq case 1 endcase |
| | <-width <-prevWidth neq case 1 endcase |
| | <-checkN <-prevCheckN neq case 1 endcase |
| | <-checkR <-prevCheckR neq case 1 endcase |
| | <-placeA <-prevPlaceA neq case 1 endcase |
| | <-placeV <-prevPlaceV neq case 1 endcase |
| | <-cell ev2 <-prevSearchCell ev2 distancecell <-groupDist gt if 1 else |
| | <-prevSearchCell ->cell # Group the search query together with previous closeby queries. |
| | <-ignoreCooldown if getgametickcount else getgameupdatecount endif dup <-prevSearchUpdateCount gt if |
| | ->prevSearchUpdateCount |
| | 1 |
| | else |
| | pop |
| | 0 |
| | endif |
| | endif |
| | endswitch if # If the search starts from scratch, then the new settings have to be remembered for the next function call. |
| | # Adjust the footprint array depending on the requested width in the function call. |
| | <-width dup <-prevWidth neq if |
| | dup ->prevWidth |
| | 1 add 2.0 div asint dup ->rHz 1 sub ->rLz # For the footprint loop. |
| | else pop endif |
| | <-length dup <-prevLength neq if |
| | dup ->prevLength |
| | 1 add 2.0 div asint dup ->rHx 1 sub ->rLx # For the footprint loop. |
| | else pop endif |
| | |
| <-anywhereList <-unitType listcontains if | <-checkN ->prevCheckN |
| 1 ->resetSnapIndex | <-checkR ->prevCheckR |
| 1 else 0 endif ->placeA | <-placeA ->prevPlaceA |
| | <-placeV ->prevPlaceV |
| | <-cell ->prevSearchCell |
| | |
| <-voidList <-unitType listcontains if | # Recreate the area to search: |
| 1 ->resetSnapIndex | <-searchRange 1 sub dup dup mul swap add 2 mul 1 add ->floodRange |
| 1 else 0 endif ->placeV | <-cell ev2 0 21 <-floodRange floodfillterrain dup getlistcount ->potentialCellCount ->potentialCells |
| | 0 ->startLoop |
| | 0 ->skip |
| | |
| # 0 ->orientation | # If the search is being reset, then clear the blocked lists. |
| endif | <-resetSearch If |
| <-unitType ->prevType | 1 ->ignoreCooldown |
| | <-blockedCellV2List clearlist |
| <-cell ev2 <-prevSearchCell ev2 distancecell 6 gt if | <-blockedCellTimeList clearlist |
| <-cell ->prevSearchCell | <-nearbyUnitsCountList clearlist |
| 1 ->resetSnapIndex | endif |
| else | else |
| <-prevSearchCell ->cell | # If the search continues on from before, but the search range was changed, then the potential cells will need to be adjusted, without adjusting the start of the loop. |
| getgameupdatecount dup 29 sub <-prevSearchUpdateCount gt if | <-searchRange dup <-prevSearchRange neq if |
| ->prevSearchUpdateCount | dup ->prevSearchRange 1 sub dup dup mul swap add 2 mul 1 add ->floodRange |
| 1 ->resetSnapIndex | <-cell ev2 0 21 <-floodRange floodfillterrain dup getlistcount ->potentialCellCount ->potentialCells |
| else pop endif | else pop endif |
| endif | endif |
| | |
| # If the new searchRange is larger than the old one, then do not check if the cell was blocked. | # If the new searchRange is larger than the old one, then do not check if the cell was recently found to be blocked. |
| <-searchRange <-prevSearchRange gt not if | <-searchRange <-prevSearchRange gt <-ignoreCooldown OR not if |
| # Check if the cell is not on the list of blocked cells. | # Check if the cell is not on the list of blocked cells. |
| <-blockedCellV2List <-cell getlistindex dup -1 neq if | <-blockedCellV2List <-cell getlistindex dup -1 eq if pop else |
| ->index | ->index |
| | |
| # Try to purge a few old blocked cells from the lists, otherwise the list bloats. | # Try to purge a few old blocked cells from the lists, otherwise the list bloats. |
| -1 <-index 1 sub do | -1 <-index 1 sub do |
| <-blockedCellTimeList[i] 270 add <-updateCount lt if | <-blockedCellTimeList[i] <-clearCellTimer add <-updateCount lt if |
| <-blockedCellV2List i removelistelement | <-blockedCellV2List i removelistelement |
| <-blockedCellTimeList i removelistelement | <-blockedCellTimeList i removelistelement |
| switch | switch |
| # The last check on this spot was less than 3 seconds ago, so we're not checking it again now. | # The last check on this spot was less than 3 seconds ago, so we're not checking it again now. |
| <-blockedCellTimeList[<-index] 90 add <-updateCount gt case | <-blockedCellTimeList[<-index] <-blockCellTimer add <-updateCount gt case |
| -1 -1 v2 false return | -1 -1 v2 false return |
| endcase | endcase |
| # The last check on this spot was longer than 9 seconds ago, so we can check it again now. | # The last check on this spot was longer than 9 seconds ago, so we can check it again now. |
| <-blockedCellTimeList[<-index] 270 add <-updateCount lt case | <-blockedCellTimeList[<-index] <-clearCellTimer add <-updateCount lt case |
| <-blockedCellV2List <-index removelistelement | <-blockedCellV2List <-index removelistelement |
| <-blockedCellTimeList <-index removelistelement | <-blockedCellTimeList <-index removelistelement |
| <-nearbyUnitsCountList <-index removelistelement | <-nearbyUnitsCountList <-index removelistelement |
| endcase | endcase |
| # Check the cell again if there are now less enemies nearby than when it was blocked. | # Check the cell again if there are now less units nearby than when it was blocked. |
| <-cell ev2 fromcell 13 0 0 0 0 0 0 getunitsinrange getlistcount <-nearbyUnitsCountList[<-index] lt case | <-cell ev2 fromcell 13 0 0 0 0 0 0 getunitsinrange getlistcount <-nearbyUnitsCountList[<-index] lt case |
| <-blockedCellV2List <-index removelistelement | <-blockedCellV2List <-index removelistelement |
| -1 -1 v2 false return | -1 -1 v2 false return |
| endswitch | endswitch |
| else pop endif | endif |
| endif | |
| | |
| <-ignoreCooldown <-resetSnapIndex OR if | |
| <-searchRange 1 sub dup dup mul swap add 2 mul 1 add ->floodRange | |
| <-cell ev2 0 21 <-floodRange floodfillterrain dup getlistcount ->potentialCellCount ->potentialCells | |
| 0 ->startLoop | |
| 0 ->resetSnapIndex | |
| else | |
| # If the search range was changed, then the potentialcells will need to be adjusted, without adjusting the start of the loop. | |
| <-searchRange dup <-prevSearchRange neq if | |
| dup ->prevSearchRange 1 sub dup dup mul swap add 2 mul 1 add ->floodRange | |
| <-cell ev2 0 21 <-floodRange floodfillterrain dup getlistcount ->potentialCellCount ->potentialCells | |
| else pop endif | |
| endif | endif |
| | |
| <-potentialCells[i] ev2 @checkCenterCell if | <-potentialCells[i] ev2 @checkCenterCell if |
| @checkFootPrint if | @checkFootPrint if |
| i <-width 2 div asint add ->startLoop # Optimization for mass placement. When placing the next unit, skip the next 4 potential cells by increasing the index with 3. | i <-noSkip if 1 else <-width endif add ->startLoop # Optimization for mass placement. When placing the next unit, skip the next few potential cells. |
| <-potentialCells[i] true return # LEFT ON STACK. | <-potentialCells[i] true return # LEFT ON STACK. |
| endif | endif |
| else | else |
| <-width 3 div asint ->skip # If the center cell was blocked, then also skip the next X potential cells. | <-noSkip if 0 else <-width 3 div asint 1 add endif ->skip # If the center cell was blocked, then also skip the next X potential cells. |
| endif | endif |
| endif | endif |
| <-special <-meshSlot eq <-height 0 eq AND case pop pop false return endcase # Units cannot be placed on mesh in the void. | <-special <-meshSlot eq <-height 0 eq AND case pop pop false return endcase # Units cannot be placed on mesh in the void. |
| ->cZ ->cX # Used in checkFootPrint. | ->cZ ->cX # Used in checkFootPrint. |
| <-checkR if <-special <-resourceSlot eq if <-minePot 1 add ->minePot endif endif | <-checkR if <-special <-resoSlot eq if |
| | <-minePot 1 add ->minePot |
| | else |
| | pop pop false return |
| | endif endif |
| | <-checkO if |
| | "platform" <-cX 0 <-cZ v3 2.9 0 0 0 2 1 0 getunits 0 getlistelement |
| | getunitcell <-cX <-cZ distancecell 2.828427 approximately not if pop pop false return endif |
| | endif |
| | <-checkN if |
| | <-suppressList <-cX 0 <-cZ v3 9 0 0 0 1 0 1 getunitsinrange 0 getlistelement getunittype listcontains not if pop pop false return endif |
| | endif |
| endswitch | endswitch |
| true | true |
| # Adjust the footprint array depending on the requested width in the function call. | |
| <-width dup <-prevWidth neq if | |
| dup ->prevWidth | |
| 1 add 2.0 div asint dup ->rH 1 sub ->rL | |
| else pop endif | |
| |
| :checkFootPrint | :checkFootPrint |
| # See vanilla snapping tool cpack for full commments. | # See vanilla snapping tool cpack for full commments. |
| | |
| <-cZ <-rH add <-cZ <-rL sub do | <-cZ <-rHz add <-cZ <-rLz sub do |
| <-cX <-rH add <-cX <-rL sub do | <-cX <-rHx add <-cX <-rLx sub do |
| switch | switch |
| i j | i j |
| <-placeA case pop pop endcase | <-placeA case pop pop endcase |
| pop pop | pop pop |
| | <-checkR if <-special <-resoSlot eq if <-minePot 1 add ->minePot endif endif |
| endswitch | endswitch |
| <-checkR if <-special <-resourceSlot eq if <-minePot 1 add ->minePot endif endif | |
| loop | loop |
| loop | loop |
| <-checkN if <-suppressList <-cX 0 <-cZ v3 9 0 0 0 1 0 1 getunitsinrange 0 getlistelement getunittype listcontains not if false return endif endif | <-checkR if <-minePot <-length <-width mul 0.75 mul lt if false return endif endif |
| <-checkR if <-minePot <-rangeX 2 mul 1 sub <-rangeZ 2 mul 1 sub mul 0.75 mul lt if false return endif endif | |
| # <-checkO if | |
| # "platform" <-cX 0 <-cZ v3 2.9 0 0 0 2 1 0 getunits 0 getlistelement | |
| # getunitcell <-cX <-cZ distancecell 2.828427 approximately not if false return endif | |
| # endif | |
| true | true |
| | |
| | </code> |
| | |
| | </hidden> |
| | |
| | ---- |
| | |
| | |
| | ===== Pseudo Random Number Generator, based on sinus ===== |
| | |
| | <hidden click here for source code> |
| | |
| | Several basic functions based on Grabz' rng lite function of "<-seed sinus 10000 mul dup floor sub". |
| | |
| | |
| | Copy the functions directly into your script or run the below script in your cpack and have other scripts send messages to request a randfloat or randint. |
| | |
| | |
| | Difference between the functions: |
| | * "seeded": provide a seed to the generator. The generated number will always be the same for the same seed. |
| | * "index": these seeds are sequenced. In the same map for the same index, the generated number 1,2,3,... will be the same after map restart. |
| | * "spiked": the generator is seeded with the elapsedtime of the gaming session, making the outcome uncontrollable. |
| | * No prefix: these are sequenced seeds and they use index 0. |
| | |
| | Example code with messages: |
| | <code example.4rpl> |
| | 12345 ->int |
| | 1 ->index |
| | 0 ->first |
| | 100 ->last # The last integer itself will be excluded. |
| | "sinRandInt" <-first <-last 2 listN sendmsg |
| | <-*sinRandInt trace |
| | "sinRand01" 0 sendmsg |
| | <-*sinRand01 trace |
| | "indexSinRandInt" <-index <-first <-last 3 listN sendmsg |
| | <-*sinRandInt trace |
| | "indexSinRand01" <-index sendmsg |
| | <-*sinRand01 trace |
| | "spikedSinRandInt" <-first <-last 2 listN sendmsg |
| | <-*sinRandInt trace |
| | "spikedSinRand01" 0 sendmsg |
| | <-*sinRand01 trace |
| | "seededSinRandInt" <-seed <-first <-last 3 listN sendmsg |
| | <-*sinRandInt trace |
| | "seededSinRand01" <-seed sendmsg |
| | <-*sinRand01 trace |
| | </code> |
| | |
| | ---- |
| | |
| | <code SinusRNG.4rpl> |
| | :once |
| | # Creating lists and a starting mapconstant for the seed sequences. |
| | createlist ->prevSeedList |
| | createlist ->seedCountList |
| | getmapsize 2 div swap 2 div swap dup2 getterrain 1 add dup 99999 floodfillterrain getlistcount ->mapConstant |
| | |
| | # Setting up the messages so that other scripts can request a random seed. |
| | "sinRandInt" "sinRandInt_CALL" registerformsg |
| | "sinRand01" "sinRand01_CALL" registerformsg |
| | "indexSinRandInt" "indexSinRandInt_CALL" registerformsg |
| | "indexSinRand01" "indexSinRand01_CALL" registerformsg |
| | "spikedSinRandInt" "spikedSinRandInt_CALL" registerformsg |
| | "spikedSinRand01" "spikedSinRand01_CALL" registerformsg |
| | "seededSinRandInt" "seededSinRandInt_CALL" registerformsg |
| | "seededSinRand01" "seededSinRand01_CALL" registerformsg |
| | |
| | :sinRandInt_CALL |
| | <-_DATA listtostack @sinRandInt ->*sinRandInt |
| | :sinRand01_CALL |
| | @sinRand01 ->*sinRand01 |
| | :indexSinRandInt_CALL |
| | <-_DATA listtostack @indexSinRandInt ->*sinRandInt |
| | :indexSinRand01_CALL |
| | <-_DATA @indexSinRand01 ->*sinRand01 |
| | :spikedSinRandInt_CALL |
| | <-_DATA listtostack @spikedSinRandInt ->*sinRandInt |
| | :spikedSinRand01_CALL |
| | @spikedSinRand01 ->*sinRand01 |
| | :seededSinRandInt_CALL |
| | <-_DATA listtostack @seededSinRandInt ->*sinRandInt |
| | :seededSinRand01_CALL |
| | <-_DATA @seededSinRand01 ->*sinRand01 |
| | |
| | :sinRandInt # INPUT: integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt. |
| | ->last |
| | ->first |
| | 0 @indexSinRand01 <-last <-first sub mul <-first add asint |
| | |
| | :sinRand01 # INPUT: none. |
| | 0 @indexSinRand01 |
| | |
| | :indexSinRandInt # INPUT: the index of the seed sequence + integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt. |
| | ->last |
| | ->first |
| | @indexSinRand01 <-last <-first sub mul <-first add asint |
| | |
| | :indexSinRand01 # INPUT: the "index" of the seed sequence. OUTPUT: the next seed from that sequence. |
| | # The random numbers will be generated with the previous seed that is stored under that index. |
| | ->i |
| | <-prevSeedList[<-i] eq0 if |
| | <-i <-mapConstant add 2357 mul @seededSinRand01 dup 10000000 mul 1 add asint ->prevSeedList[<-i] |
| | 1 ->seedCountList[<-i] |
| | else |
| | <-prevSeedList[<-i] @seededSinRand01 dup 10000000 mul <-seedCountList[<-i] 1 add dup ->seedCountList[<-i] add asint ->prevSeedList[<-i] |
| | endif |
| | |
| | :spikedSinRandInt # INPUT: integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt. |
| | ->last |
| | ->first |
| | elapsedtime asint @seededSinRand01 <-last <-first sub mul <-first add asint |
| | |
| | :spikedSinRand01 |
| | elapsedtime asint @seededSinRand01 |
| | |
| | :seededSinRandInt # INPUT: integer seed + integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt. |
| | ->last |
| | ->first |
| | @seededSinRand01 <-last <-first sub mul <-first add asint |
| | |
| | :seededSinRand01 |
| | # abs <-power pow <-add add sin <-sinMul mul dup floor sub |
| | 1.05 pow 99419 sub sin 619 mul dup floor sub |
| | </code> |
| | |
| | </hidden> |
| | |
| | ---- |
| | |
| | |
| | ===== Pseudo RNG based on Linear congruential generator (LCG) ===== |
| | |
| | The downside of the above sinus based functions, is that they have few resulting digits. They are more than adequate to pick a random cell on a map (max 512 possibilities), but unsuited for generating random numbers larger than a few thousand. |
| | |
| | The below LCG functions should work for up to 6 digits = 10^6, but I've only done a test up to 10^5. Rolling 1 million random integers between 0 and 10^5 with @lcgRandInt, missed only 8 possible results. The same test with @sinRandInt missed 42458 possible results. |
| | |
| | <hidden click here for source code> |
| | |
| | <code LinearCongruentialGenerator.4rpl> |
| | # Quick Example. |
| | 0 10000 @lcgRandInt |
| | |
| | :lcgRandInt # INPUT: integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt. |
| | ->last |
| | ->first |
| | 0 @indexLcgRand01 <-last <-first sub mul <-first add asint |
| | |
| | :lcgRand01 # INPUT: none. |
| | 0 @indexLcgRand01 |
| | |
| | :indexLcgRandInt # INPUT: the index of the seed sequence + integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt. |
| | ->last |
| | ->first |
| | @indexLcgRand01 <-last <-first sub mul <-first add asint |
| | |
| | :indexLcgRand01 # INPUT: the "index" of the seed sequence. OUTPUT: the next seed from that sequence. |
| | # The random numbers will be generated with the previous seed that is stored under that index. |
| | ->i |
| | <-prevSeedList[<-i] eq0 if |
| | <-i <-mapConstant add 13 mul @seededLcgRand01 dup 10000000 mul 1 add asint ->prevSeedList[<-i] |
| | 1 ->seedCountList[<-i] |
| | else |
| | <-prevSeedList[<-i] @seededLcgRand01 dup 10000000 mul <-seedCountList[<-i] 1 add dup ->seedCountList[<-i] add asint ->prevSeedList[<-i] |
| | endif |
| | |
| | :clearIndexSinLcgRand |
| | <-seedCountList clearlist |
| | <-prevSeedList clearlist |
| | |
| | :spikedLcgRandInt # INPUT: integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt. |
| | ->last |
| | ->first |
| | elapsedtime dup 10 log ceil 7 sub neg 10 swap pow mul asint <-spikedLcgAdd 1 add dup ->spikedLcgAdd mul @seededLcgRand01 <-last <-first sub mul <-first add asint |
| | |
| | :spikedLcgRand01 |
| | elapsedtime dup 10 log ceil 7 sub neg 10 swap pow mul asint <-spikedLcgAdd 1 add dup ->spikedLcgAdd mul @seededLcgRand01 |
| | |
| | :seededLcgRandInt # INPUT: integer seed + integer first randInt + integer last randInt. OUTPUT: an integer in between the first and last randInt, excluding the last randInt. |
| | ->last |
| | ->first |
| | @seededLcgRand01 <-last <-first sub mul <-first add asint |
| | |
| | :seededLcgRand01 |
| | asint 1103515245 mul 12345 add abs asfloat 2147483647 div 10 mul dup floor sub |
| | |
| | :once |
| | # Creating lists and a starting mapconstant for the seed sequences. |
| | createlist ->prevSeedList |
| | createlist ->seedCountList |
| | getmapsize 2 div swap 2 div swap dup2 getterrain 1 add dup 99999 floodfillterrain getlistcount ->mapConstant |
| | # <-SEED ->mapconstant # Alternative for map constant. |
| </code> | </code> |
| |