Skip to content

Instantly share code, notes, and snippets.

@DonBattery
Last active October 10, 2025 11:32
Show Gist options
  • Select an option

  • Save DonBattery/511e0a41756c04515e6fac8d21c594b8 to your computer and use it in GitHub Desktop.

Select an option

Save DonBattery/511e0a41756c04515e6fac8d21c594b8 to your computer and use it in GitHub Desktop.
Golang implementation of Yusuke Endoh ASCII Fluid
// this is the Golang version of
// Davide Della Casa's de-obfuscated version https://github.com/davidedc/Ascii-fluid-simulation-deobfuscated
// original obfuscated version by Yusuke Endoh
package main
import (
"bytes"
"fmt"
"io"
"math"
"os"
"time"
)
const (
console_width = 80
console_height = 24
)
type Particle struct {
xPos float64
yPos float64
density float64
wallflag float64
xForce float64
yForce float64
xVelocity float64
yVelocity float64
}
var (
xSandboxAreaScan = 0
ySandboxAreaScan = 0
particles = make([]Particle, console_width*console_height*2)
xParticleDistance float64 = 0
yParticleDistance float64 = 0
particlesInteraction float64 = 0
particlesDistance float64 = 0
totalParticles int = 0
gravity float64 = 1
pressure float64 = 4
viscosity float64 = 7
lookup = " '`-.|//,\\|\\_\\/#" // 16 chars, index 0..15
)
func main() {
// Clear the screen once
fmt.Print("\x1b[2J")
// read the input file to create the walls and particles.
bytesIn, err := io.ReadAll(os.Stdin)
if err != nil {
panic(err)
}
particlesCounter := 0
for i := 0; i < len(bytesIn); i++ {
ch := bytesIn[i]
switch ch {
case '\n':
ySandboxAreaScan += 2
xSandboxAreaScan = -1
case ' ':
// nothing
case '#':
// mark as wall on two particles (guard check)
if particlesCounter+1 < len(particles) {
particles[particlesCounter].wallflag = 1
particles[particlesCounter+1].wallflag = 1
// set their positions too (so they exist in space)
particles[particlesCounter].xPos = float64(xSandboxAreaScan)
particles[particlesCounter].yPos = float64(ySandboxAreaScan)
particles[particlesCounter+1].xPos = float64(xSandboxAreaScan)
particles[particlesCounter+1].yPos = float64(ySandboxAreaScan) + 1
particlesCounter += 2
totalParticles += 2
}
default:
// any other non-space char creates two particles one above the other
if particlesCounter+1 < len(particles) {
particles[particlesCounter].xPos = float64(xSandboxAreaScan)
particles[particlesCounter].yPos = float64(ySandboxAreaScan)
particles[particlesCounter+1].xPos = float64(xSandboxAreaScan)
particles[particlesCounter+1].yPos = float64(ySandboxAreaScan) + 1
particlesCounter += 2
totalParticles += 2
}
}
xSandboxAreaScan++
}
// flags buffer: holds 0..15 masks for each character cell
flags := make([]byte, console_width*console_height)
for {
// densities
for p := 0; p < totalParticles; p++ {
particles[p].density = particles[p].wallflag * 9
for q := 0; q < totalParticles; q++ {
xParticleDistance = particles[p].xPos - particles[q].xPos
yParticleDistance = particles[p].yPos - particles[q].yPos
particlesDistance = math.Sqrt(xParticleDistance*xParticleDistance + yParticleDistance*yParticleDistance)
particlesInteraction = particlesDistance/2 - 1
if math.Floor(1-particlesInteraction) > 0 {
particles[p].density += particlesInteraction * particlesInteraction
}
}
}
// forces
for p := 0; p < totalParticles; p++ {
particles[p].yForce = gravity
particles[p].xForce = 0
for q := 0; q < totalParticles; q++ {
xParticleDistance = particles[p].xPos - particles[q].xPos
yParticleDistance = particles[p].yPos - particles[q].yPos
particlesDistance = math.Sqrt(xParticleDistance*xParticleDistance + yParticleDistance*yParticleDistance)
particlesInteraction = particlesDistance/2 - 1
if math.Floor(1-particlesInteraction) > 0 {
particles[p].xForce += particlesInteraction * (xParticleDistance*(3-particles[p].density-particles[q].density)*pressure + particles[p].xVelocity*viscosity - particles[q].xVelocity*viscosity) / particles[p].density
particles[p].yForce += particlesInteraction * (yParticleDistance*(3-particles[p].density-particles[q].density)*pressure + particles[p].yVelocity*viscosity - particles[q].yVelocity*viscosity) / particles[p].density
}
}
}
// clear flags (0..15 per cell)
for i := range flags {
flags[i] = 0
}
// update particles
for p := 0; p < totalParticles; p++ {
if particles[p].wallflag == 0 {
forceMag := math.Sqrt(particles[p].xForce*particles[p].xForce + particles[p].yForce*particles[p].yForce)
if forceMag < 4.2 {
particles[p].xVelocity += particles[p].xForce / 10
particles[p].yVelocity += particles[p].yForce / 10
} else {
particles[p].xVelocity += particles[p].xForce / 11
particles[p].yVelocity += particles[p].yForce / 11
}
particles[p].xPos += particles[p].xVelocity
particles[p].yPos += particles[p].yVelocity
}
px := int(particles[p].xPos)
py := int(particles[p].yPos) / 2 // scale correction
if py >= 0 && py < console_height && px >= 0 && px < console_width {
idx := py*console_width + px
// set 4 bits mapping
// top-left -> 8, top-right -> 4, bottom-left -> 2, bottom-right -> 1
flags[idx] |= 8
if px+1 < console_width {
flags[idx+1] |= 4
}
if py+1 < console_height {
flags[idx+console_width] |= 2
if px+1 < console_width {
flags[idx+console_width+1] |= 1
}
}
}
}
// build output string row by row
var b bytes.Buffer
for row := 0; row < console_height; row++ {
for col := 0; col < console_width; col++ {
b.WriteByte(lookup[flags[row*console_width+col]])
}
b.WriteByte('\n')
}
// move cursor home and print
fmt.Print("\x1b[1;1H")
fmt.Print(b.String())
// sleep for 16 milliseconds, which is about 60 fps
time.Sleep(16 * time.Millisecond)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment