I do love that I can open C3/DS cos scripts and get the initial injection code, rather than having to backwards engineer it as in C1 and C2. Given how complicated many of the objects in C3/DS are, this sort of modification may well have been impossible without this small change in code accessibility! Here's the initial shark script - with a lot of unnecessary empty lines removed. Actually, this whole script seems to contain a lot of unnecessary stuff, such as a reps loop that runs once, or object variables that are set but never referenced. It feels very much like code that was never cleaned up after they got it functional.
The Original Script
For reference, the object variables that we care about are as follows:
- ov01: age
- ov02: fullness; the lower it is the hungrier the shark is
- ov05: adulthood flag; 2 indicates a mature adult.
- ov10: horizontal direction of movement
- ov11: vertical direction of movement
- ov19: lock A; makes sure a particular part of the code only runs once.
- ov20, ov21, ov22: genetic red, green, and blue values
- ov23, ov24, ov25: record of mate's red, green, and blue values.
- ov70: mated flag
- ov71: species identifier of the type of fish it hunts. If it can't find this type of fish, it'll randomly choose a different one.
- ov72: technically not unused, but it's only ever set and checked alongside ov05, and therefore seems to be redundant with it.
- ov80: marked for death flag; marks the shark to die after some time.
- ov81: death timer used in connection to the impending death flag.
- ov92: lock B; makes sure a different part of the code only runs once.
- ov99: supposed-to-be-dead flag; the shark was already supposed to be killed but for some reason couldn't be, so this flag indicates that it should be killed as soon as it's valid to do so.
Sharkling Inject Script | |
---|---|
setv va00 0 reps rand 3 5 setv va60 rand 60 255 setv va61 rand 60 255 setv va62 rand 60 255 setv va40 3879 setv va41 2192 reps 1 addv va60 1 new: simp 2 16 4 "shark" …153 0 4100 attr 199 clac -1 elas 20 aero 1 accg 0 perm 64 tint va60 va61 va62 128 128 mvto va40 va41 setv ov01 0 setv ov02 550 seta ov16 null setv va50 rand 0 1 setv ov61 40 setv ov73 1 setv ov74 0 doif va50 = 0 setv ov10 -1 pose 0 elif va50 = 1 setv ov10 1 pose 8 endi velo 0 0 setv va51 rand 1 4 doif va51 = 1 setv ov71 14 elif va51 = 2 setv ov71 15 elif va51 = 3 setv ov71 16 elif va51 = 4 setv ov71 19 endi setv ov20 va60 setv ov21 va61 setv ov22 va62 tick 1 repe repe endm | set unused variable do this 3 to 5 times: { generate red value generate green value generate blue value define starting position x define starting position y do this once: { add 1 to red value for some reason create new shark object … C/H carry, activatable, collisions and physics when clicked, send no script set bounciness set aerodynamics not affected by gravity sets what walls it can go through color according to the color values move to starting position set age to 0 set fullness to 550 save an unused reference generate random cointoss value save an unused value save an unused value save an unused value if cointoss is 0 { set direction to left left-facing sprite } else if cointoss is 1 { set direction to right right-facing sprite } no velocity generate randomizer between 1 and 4 if randomizer is 1 { set prey type to angelfish } else if randomizer is 2 { set prey type to clownfish } else if randomizer is 3 { set prey type to hatchetfish } else if randomizer is 4 { set prey type to neonfish } save genetic red value save genetic green value save genetic blue value set timer to one tick } } |
Most of the shark's behavior is contained in the timer script, with several subroutines for different activities. Some of them aren't relevant to this project, so I'm not going to annotate them. Some of these are fairly self-explanatory: DETH kills the shark, OBST handles collisions with obstacles, GORT sets the velocity to the right and plays the rightward swimming animation, etc. Others require a little more explanation but don't merit an in-depth explanation: ROOM detects whether the shark is in salt water and adjusts its physics accordingly, and eventually kills the shark if out of water too long. INIM handles the shark's velocity. MARK literally just sets the impending death flag. RNDM randomly changes the direction the shark is moving in. FLOK allows the sharks to swim together in a school.
Sharkling Timer Script | |
---|---|
scrp 2 16 4 9 gsub room doif ov99 = 5 gsub deth endi doif ov01 >= 3000 gsub deth endi addv ov01 1 doif ov01 > 1200 and ov19 = 0 setv ov05 2 setv ov72 0 setv ov19 1 endi subv ov02 1 doif ov70 = 1 gsub repr setv ov70 0 gsub mark endi doif ov80 = 1 addv ov81 1 doif ov81 = 15 gsub deth endi endi doif ov05 = 2 and ov72 = 0 …and ov80 <> 1 gsub mate endi doif ov02 >= 500 doif ov92 = 0 gsub inim gsub obst gsub swim gsub move setv ov92 1 elif ov92 = 1 setv va00 rand 1 5 doif va00 <= 2 gsub flok else gsub rndm endi gsub obst gsub swim gsub move endi elif ov02 < 500 and ov02 > 0 gsub hunt gsub obst gsub swim gsub move gsub eat_ elif ov02 <= 0 gsub deth endi | SHARKLING TIMER SCRIPT run ROOM subroutine if the shark is supposed to be dead { run DETH subroutine } if age >= 3000 { run DETH subroutine } add 1 to age if age > 1200 and lock A isn't set { set adulthood flag set redundant adulthood flag set lock A } decrease fullness by 1 if mated { run REPR subroutine set not mated run MARK subroutine } if marked for death { add 1 to death timer if death timer = 15 { run DETH subroutine } } if adult and redundantly adult …and not marked for death { run MATE subroutine } if fullness >= 500 { if lock B not set { run INIM subroutine run OBST subroutine run SWIM subroutine run MOVE subroutine set lock B } else if lock B is set { generate randomizer between 1 and 5 if randomizer <= 2 { run FLOK subroutine } else { run RNDM subroutine } run OBST subroutine run SWIM subroutine run MOVE subroutine } } else if fullness between 500 and 0 { run HUNT subroutine run OBST subroutine run SWIM subroutine run MOVE subroutine run EAT subroutine } else if fullness <= 0 run DETH subroutine } |
subr mate rnge 100 inst esee 2 16 4 doif targ <> null doif ov05 = 2 setv va70 1 setv va20 ov20 setv va21 ov21 setv va22 ov22 endi endi next slow doif va70 = 1 setv ov70 1 setv ov23 va20 setv ov24 va21 setv ov25 va22 endi retn | MATE SUBROUTINE set the range for detection run this section instantly for each shark in range { if target isn't null { if target is adult { locally set mated flag locally save target's red value locally save target's green value locally save target's blue value } } } end instantaneous section if local mated flag { set real mated flag save mate's red value save mate's green value save mate's blue value } end subroutine |
subr repr setv ov08 0 setv va34 0 rnge 1000 esee 2 16 4 addv va34 1 next doif va34 <= 5 setv ov08 rand 1 20 doif ov08 = 5 setv ov08 rand 1 3 else setv ov08 rand 1 2 endi else setv ov08 0 endi doif ov08 <> 0 reps ov08 setv va00 rand 1 5 doif va00 = 5 setv va20 rand 06 255 addv va20 ov20 divv va20 2 doif va20 < 50 addv va20 20 endi else setv va20 ov20 endi setv va00 rand 1 5 doif va00 = 5 setv va21 rand 60 255 addv va21 ov21 divv va21 2 doif va20 < 50 addv va20 20 endi else setv va21 ov21 endi setv va00 rand 1 5 doif va00 = 5 setv va22 rand 60 255 addv va22 ov22 divv va22 2 doif va20 < 50 addv va20 20 endi else setv va22 ov22 endi addv va26 ov20 addv va26 ov23 addv va26 va20 addv va27 ov21 addv va27 ov24 addv va27 va21 addv va28 ov22 addv va28 ov25 addv va28 va22 divv va26 3 divv va27 3 divv va28 3 setv va80 posl setv va81 post new: simp 2 16 4 "shark" …153 0 4100 attr 199 clac -1 elas 20 aero 1 accg 0 perm 64 tint va26 va27 va28 128 128 mvto va80 va81 setv ov01 0 setv ov02 550 seta ov16 null setv va50 rand 0 1 setv ov61 40 setv va51 rand 1 4 doif va51 = 1 setv ov71 14 elif va51 = 2 setv ov71 15 elif va51 = 3 setv ov71 16 elif va51 = 4 setv ov71 19 endi setv ov73 1 setv ov74 0 doif va50 = 0 setv ov10 -1 pose 0 elif va50 = 1 setv ov10 1 pose 8 endi setv ov20 va26 setv ov21 va27 setv ov22 va28 velo 0 0 tick 1 repe targ ownr setv ov70 0 gsub mark endi retn | REPR SUBROUTINE set number of offspring to 0 set shark counter to 0 set long range for each detected shark { add 1 to shark counter } if shark counter <= 5 { set number of offspring between 1 and 20 if number of offspring is 5 { set number of offspring between 1 and 3 } else { set number of offspring between 1 and 2 } } else { set number of offspring to 0 } if number of offspring is not 0 { for that number of times { set mutation flag between 1 and 5 if mutation flag = 5 { set temp red between 6 and 255 add parent's red value divide by 2 (for average) if temp red < 50 { add 20 to temp red } } else { set temp red to parent red } set mutation flag between 1 and 5 if mutation flag = 5 { set temp green between 60 and 255 add parent's green value divide by 2 (for average) if temp red < 50 { add 20 to temp red } } else { set temp green to parent green } set mutation flag between 1 and 5 if mutation flag = 5 { set temp blue between 60 and 255 add parent's blue value divide by 2 (for average) if temp red < 50 { add 20 to temp red } } else { set temp blue to parent blue } add parent red to offspring red add mate red to offspring red add temp red to offspring red add parent green to offspring green add mate green to offspring green add temp green to offspring green add parent blue to offspring blue add mate blue to offspring blue add temp blue to offspring blue divide offspring red by 3 (for average) divide offspring green by 3 (for average) divide offspring blue by 3 (for average) save left position save top position create new shark object … C/H carry, activatable, collisions, physics when clicked send no script set bounciness set aerodynamics not affected by gravity sets what walls it can go through color according to offspring colors move to saved position set age to 0 set fullness to 550 save an unused reference generate random cointoss value save an unused value generate randomizer between 1 and 4 if randomizer is 1 { set prey type to angelfish } else if randomizer is 2 { set prey type to clownfish } else if randomizer is 3 { set prey type to hatchetfish } else if randomizer is 4 { set prey type to neonfish } save an unused value save an unused value if cointoss is 0 { set direction to left left-facing sprite } else if cointoss is 1 { set direction to right right-facing sprite } save genetic red value save genetic green value save genetic blue value no velocity set timer to one tick } target the parent again set not mated run MARK subroutine } end subroutine |
There are numerous errors in the reproduction script. A typo causes the temporary red value to be generated between 6 and 255 rather than 60 and 255. When the green and blue values mutate, they erroneously verify that the red value is greater than 50 rather than checking themselves. Not to mention, even when they do mutate, the mutated color is averaged with the parent's value, and then averaged again with the parent and the mate, severely muting it.
Simulation and Algorithm Selection
Because it would take a while to breed generations of sharks for testing, I figured it would be wise to write a program that simulated the shark genetics quickly. This script cuts the shark behavior down to the essentials - instead of tracking hunger and hunting, I just occasionally kill a shark at random to simulate starvation; instead of tracking whether sharks are near enough to each other to mate, I just use another random number to determine if a mate is successfully found, and then select a second shark randomly from the list. Aging, reaching adulthood, and dying of old age or after mating are simulated.
The program was initially text-only, but seeing a wall of numbers wasn't nearly as helpful as seeing actual colors, so I added some limited graphical capabilities: a grid of boxes, each representing the color of a specific shark (with its ID number written on it for clarity). Each row, starting at the top, represents a point in time - a new row is printed whenever a shark dies or is born.
The Original Algorithm
As expected, implementing the existing algorithm results in a population that doesn't take long to converge on a single dull color, such as the ones I've seen in game.
The Fixed Original Algorithm
The original algorithm had several errors, as I've pointed out. I decided to see what would happen if I left the overall structure intact, but fixed the obvious mistakes (correcting 06 to 60, making sure the mutation code actually checks the correct color, etc).
These corrections do result in brighter colors! However, they still converge on a single color relatively quickly, making for a boring population.
The "Wild Mutation" Algorithm
My first attempt at improving the algorithm was to do away with the complexities of averaging the mutations into the parent colors, allowing them to be more dramatic. I changed the color calculation to average the parent colors first, and then randomly determining whether to mutate afterwards. If the color did mutate, it did so to a random value between 60 and 255, completely unrelated to the parent colors, end of story.
As expected, my wild mutations resulted in a population that changed considerably over time, showing off a variety of usually-bright colors. Unfortunately, the individuals within the population generally were fairly similar to each other, with the occasional mutated oddball providing genetic drift rather than inducing variety in the population.
The Weighted Mutation Algorithm
I made one more edit to my algorithm to specifically combat samey-ness in the population. When determining whether to mutate the offspring, I cheated a little bit: for each color channel, if the parents' color values were particularly close to each other, I forced a mutation. This way, parents with wildly different colors will produce logically averaged offspring, but if they're too similar, the gene pool gets a new injection of color.
Now that is the desired result! The colors aren't completely random; a red shark and a blue shark will produce purple offspring, giving a sense of continuity to the population, but at the same time, variety is enforced enough to truly earn the name rainbow sharkling!
If you'd like to play with the python script I used to simulate the populations, you can download it here. You'll need python 3.8 and the tkinter library installed to use it, and you can switch between the algorithms I tested by changing self.weighteddmutationCalcColor() on line 209 to self.originalCalcColor(), self.fixedCalcColor(), or self.wildmutationCalcColor().
The Alteration
With a new algorithm selected, I modified the sharkling script and injected it into the world. We shall see how it pans out! I only changed the REPR subroutine, basically cutting out the entire color calculation chunk and replacing it with all new code.
Modified Sharkling REPR Script | |
---|---|
… doif ov08 <> 0 reps ov08 setv va00 rand 1 4 setv va26 ov20 addv va26 ov23 divv va26 2 setv va20 ov20 subv va20 va26 absv va20 doif va20 < 15 setv va00 4 endi doif va00 = 4 setv va26 rand 60 255 endi setv va00 rand 1 4 setv va27 ov21 addv va27 ov24 divv va27 2 setv va21 ov21 subv va21 va27 absv va21 doif va21 < 15 setv va00 4 endi doif va00 = 4 setv va27 rand 60 255 endi setv va00 rand 1 4 setv va28 ov22 addv va28 ov25 divv va28 2 setv va22 ov21 subv va22 va28 absv va22 doif va22 < 15 setv va00 4 endi doif va00 = 4 setv va28 rand 60 255 endi setv va80 posl setv va81 post new: simp 2 16 4 "shark" 153 0 4100 attr 199 clac -1 elas 20 aero 1 accg 0 perm 64 tint va26 va27 va28 128 128 mvto va80 va81 … |
… if number of offspring is not 0 { for that number of times { set mutation flag between 1 and 4 set offspring red to parent red add mate red to offspring red divide by 2 (for average) set diff red to parent red subtract offspring red from diff red take absolute value of diff red if diff red < 15 { set mutation flag to 4 } if mutation flag = 4 { set offspring red between 60 and 255 } set mutation flag between 1 and 4 set offspring green to parent green add mate green to offspring green divide by 2 (for average) set diff green to parent green subtract offspring green from diff green take absolute value of diff green if diff green < 15 { set mutation flag to 4 } if mutation flag = 4 { set offspring green between 60 and 255 } set mutation flag between 1 and 4 set offspring blue to parent blue add mate blue to offspring blue divide by 2 (for average) set diff blue to parent blue subtract offspring blue from diff blue take absolute value of diff blue if diff blue < 15 { set mutation flag to 4 } if mutation flag = 4 { set offspring blue between 60 and 255 } save left position save top position create new shark object C/H carry, activatable, collisions, physics when clicked send no script set bounciness set aerodynamics not affected by gravity set what walls it can go through color according to offspring colors move to saved position … |
All that remains now is to observe how this modified code pans out, but that will take time, so I'll do that over the next few sessions in Eden. If I like the results, I'll add it to the downloads page. Otherwise, we've got more work to do.
incredible work
ReplyDeleteHave you ever tried the C3 Bootstrap V2? That comes with the initial developers' comments. https://creatures.wiki/C3_Bootstrap_V2
ReplyDeleteVery interesting read!
ReplyDeleteThis is super cool! Good work. I think you could've settled for the slight modified version but you persevered till you got the result you wanted. Congrats!
ReplyDelete