DEV Community

Cover image for Teaching spring-and-joint creatures to walk with a genetic algorithm (hand-written 2D physics)
sishiya (しし屋)
sishiya (しし屋)

Posted on

Teaching spring-and-joint creatures to walk with a genetic algorithm (hand-written 2D physics)

I made a page where creatures made only of joints (balls) and muscles (springs) evolve with a genetic algorithm (GA) to travel as far as possible. Besides flat ground, you can pick a slope, ice, the Moon or underwater. The physics and the GA are both hand-written, no libraries.

https://sishiya.github.io/manabi-lab/ga-creatures/?lang=en

Creatures start with random shapes and rhythms, and over the generations they find bodies and ways of moving that go forward in their environment. This post covers how the bodies and physics are built, and how I kept the GA from exploiting gaps in the physics.

Genes = the body's blueprint

One creature's genes hold:

  • positions of 3–8 joints (inside a 1 m square) and the foot friction coefficient (0.2–1.2)
  • for each muscle: the two joints it connects, contracted and extended lengths (0.15–1.6 m), spring constant (150–600 N/m), when in the cycle it starts contracting and for how long
  • the body's rhythm period (0.4–2.0 seconds)

A muscle is a damped spring (damping 0.5 of critical) that heads toward the short length during its contraction window and the long length otherwise.

Physics: 15 seconds at 1/120-second steps

  • joints are 2 kg balls, moved for 15 seconds at 1/120-second steps (1800 steps)
  • the ground pushes back penetrating joints and removes downward speed, with no bounce
  • sideways speed is reduced by friction and a "grip on the ground" force

The score is the center of mass's x after 15 seconds minus its starting x.

Environments just change gravity or friction:

Environment What changes
Slope gravity tilted 10° (the picture is rotated)
Ice friction × 0.1
Moon gravity 1.62 m/s²
Underwater gravity 0 (neutral buoyancy), quadratic drag on speed perpendicular to each muscle rod

The slope started at 15°, but even after 100 generations creatures only got 4 m, so it became 10°.

Preventing bodies from passing through themselves

The first version had no collision between body parts. That allowed motions impossible in a plane, like a joint passing through its own muscle rod. Where the physics has a gap, a GA will find solutions that use it.

So from v3, there is collision, collideBody():

  • joints are 5.5 cm-radius balls, muscles 6 cm-thick rods
  • penetration is pushed back in proportion to mass, and approaching speed is removed (no bounce)
  • each joint remembers which side of a muscle it is on, and is put back if it ends up on the other side (no tunneling even when fast)

After adding collision, I checked that momentum is conserved and that mechanical energy does not increase for a body with its muscles stopped.

Speed is about 100 ms per generation of 100 creatures (15–35 ms before collision).

A body whose physics broke scores 0

A body with strong springs might make the integration diverge and fly off. Since the score is distance traveled, that would count as "went far".

So if speed exceeds 60 m/s, the calculation is treated as broken and the score is 0.

Only bodies buildable in a plane are born

A body whose joints are too close or whose muscles cross cannot exist in a plane. So bodies are born only if they satisfy validBody():

  • joints at least 13 cm apart
  • joints at least 10.5 cm from muscles not attached to them
  • no muscles crossing

If a mutation breaks the shape, it retries up to 10 times; otherwise the parent is kept. If crossover's mixing of joint positions breaks it, positions come from one parent.

Inside the GA

Two selection methods:

  • Top half: the top half survives as is, and each makes one child
  • Tournament: only the top two survive; for the rest, the stronger of two random creatures becomes a parent

Mutations nudge values on a log-normal scale, and also change the body: adding or removing joints, adding or removing muscles.

Crossover starts from one parent's shape and takes same-numbered joints, muscles linking the same two points, and the period from the other parent with 50% chance. Between parents of different shapes, only the overlapping parts mix (a simplification).

A small gotcha

All scripts share names at the top level, so naming the body thickness BODY collided with the font setting BODY in the drawing code and stopped loading. Now I grep for any new uppercase name before using it.

Main sources

  • J. H. Holland (1975) Adaptation in Natural and Artificial Systems
  • K. Sims (1994) "Evolving Virtual Creatures", SIGGRAPH 94

This page is part of Manabi Lab, a series of small learning apps that run in the browser (English and Japanese). The code and this post were written together with AI (Claude).

https://sishiya.github.io/manabi-lab/?lang=en

Top comments (0)