Tag simulation

Simulation of cells hunting and hiding from each other. Hunter cells locate the nearest hider cell and chase after it until it tags it and removes it from the simulation. Hiders look at the position of all hunters and try to move away from all of them, prioritizing closer hunters more.

Log in to post a comment.

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(1);
console.clear()

// Global code will be evaluated once.
const turtle = new Turtle();
let seed = 1000; // min=1 max=1000000 step=1
const bits = 20; 
const samples = 150000; 
const mod = 1<<bits;
const ticks = 80; // min = 1 max = 200 step = 1
const num_hunters = 1; // min = 1 max = 20 step = 1
const num_hiders = 150; // min = 1 max = 300 step = 1
const bounds = 100; // min = 1 max = 100 step = 1
const hunter_spawn_bounds = 80; // min = 0 max = 100 step = 1
const hider_spawn_bounds = 25; // min = 0 max = 100 step = 1
const draw_hunters = 0; //min=0 max=1 step=1 (No, Yes)
const draw_hiders = 1; //min=0 max=1 step=1 (No, Yes)

////////////////////////////////////////////////////////////////
// Pseudorandom number generator. Created by Reinder Nijhoff 2024
// https://turtletoy.net/turtle/a2274fd1fe
////////////////////////////////////////////////////////////////
function random() { // returns a number [0, 1[
    let r = 1103515245 * (((seed+=12345) >> 1) ^ seed);
    r = 1103515245 * (r ^ (r >> 3));
    r = r ^ (r >> 16);
    return (r % mod) / mod;
}

class Hunter {
    constructor(radius, speed, x_pos, y_pos) {
        this.radius = radius;
        this.speed = speed;
        this.x_pos = x_pos;
        this.y_pos = y_pos;
        this.turtle = new Turtle();
    }
    
    move(list_hunters, list_hiders) {
        if(list_hiders.length <= 0) {
            return [];
        }
        let x_closest = list_hiders[0].x_pos;
        let y_closest = list_hiders[0].y_pos;
        for(let i in list_hiders) {
            if(this.distance(x_closest,y_closest,this.x_pos,this.y_pos) 
            > this.distance(list_hiders[i].x_pos,list_hiders[i].y_pos,this.x_pos,this.y_pos)){
                x_closest = list_hiders[i].x_pos;
                y_closest = list_hiders[i].y_pos;
            }
        }
        
        let x_dir = (x_closest - this.x_pos) / this.distance(x_closest,y_closest,this.x_pos,this.y_pos);
        let y_dir = (y_closest - this.y_pos) / this.distance(x_closest,y_closest,this.x_pos,this.y_pos);
        
        this.x_pos += x_dir * this.speed;
        this.y_pos += y_dir * this.speed;
        
        for(let i = list_hiders.length-1; i >= 0; i--) {
            if(this.distance(list_hiders[i].x_pos,list_hiders[i].y_pos,this.x_pos,this.y_pos) < this.radius / 1.5) {
                list_hiders.splice(i,1);
            }
        }
        
        // if(this.x_pos > bounds) {
        //     this.x_pos = bounds
        // }
        // if(this.x_pos < -bounds) {
        //     this.x_pos = -bounds
        // }
        // if(this.y_pos > bounds) {
        //     this.y_pos = bounds
        // }
        // if(this.y_pos < -bounds) {
        //     this.y_pos = -bounds
        // }
        let pos_mag = this.distance(0,0,this.x_pos,this.y_pos);
        if(pos_mag > bounds) {
            this.x_pos *= bounds / pos_mag;
            this.y_pos *= bounds / pos_mag;
        }
        
        return list_hiders;
    }
    
    draw() {
        this.turtle.penup();
        this.turtle.goto(this.x_pos, this.y_pos - this.radius);
        this.turtle.pendown();
        this.turtle.circle(this.radius);
    }
    
    distance(x1, y1, x2, y2) {
        return(Math.sqrt(((x1-x2) * (x1-x2)) + ((y1-y2) * (y1-y2))))
    }
}

class Hider {
    constructor(radius, speed, x_pos, y_pos) {
        this.radius = radius;
        this.speed = speed;
        this.x_pos = x_pos;
        this.y_pos = y_pos;
    }
    
    move(list_hunters, list_hiders) {
        let x_dir = 0;
        let y_dir = 0;
        
        for(let i in list_hunters) {
            // x_dir += 1 / ((this.x_pos - list_hunters[i].x_pos) ** 2 + 1) 
            // y_dir += 1 / ((this.y_pos - list_hunters[i].y_pos) ** 2 + 1)
            let strength = 1 / (this.distance(this.x_pos, this.y_pos, list_hunters[i].x_pos, list_hunters[i].y_pos)**2);
            x_dir += strength * (this.x_pos - list_hunters[i].x_pos)
            y_dir += strength * (this.y_pos - list_hunters[i].y_pos)
        }
        
        let magnitude = this.distance(0,0,x_dir,y_dir);
        console.log(x_dir, y_dir, magnitude)
        x_dir = x_dir / magnitude;
        y_dir = y_dir / magnitude;
        console.log(x_dir, y_dir, magnitude)
        
        this.x_pos += x_dir * this.speed;
        this.y_pos += y_dir * this.speed;
        
        // if(this.x_pos > bounds) {
        //     this.x_pos = bounds
        // }
        // if(this.x_pos < -bounds) {
        //     this.x_pos = -bounds
        // }
        // if(this.y_pos > bounds) {
        //     this.y_pos = bounds
        // }
        // if(this.y_pos < -bounds) {
        //     this.y_pos = -bounds
        // }
        let pos_mag = this.distance(0,0,this.x_pos,this.y_pos);
        if(pos_mag > bounds) {
            this.x_pos *= bounds / pos_mag;
            this.y_pos *= bounds / pos_mag;
        }
    }
    
    draw() {
        turtle.penup();
        turtle.goto(this.x_pos, this.y_pos - this.radius);
        turtle.pendown();
        turtle.circle(this.radius);
    }
    
    distance(x1, y1, x2, y2) {
        return(Math.sqrt(((x1-x2) * (x1-x2)) + ((y1-y2) * (y1-y2))))
    }
}

let hunters = [];
let hiders = [];
for(let i = num_hunters; i > 0; i--) {
    let r = 5 + random() * 3;
    let s = 2 + random() * 3;
    let radius = random() * hunter_spawn_bounds;
    let theta = random() * Math.PI * 2;
    let x = radius * Math.cos(theta);
    let y = radius * Math.sin(theta);
    let rand_hunter = new Hunter(r,s,x,y);
    hunters.push(rand_hunter);
}
for(let i = num_hiders; i > 0; i--) {
    let r = 0.2 + random() * 0.4;
    // let s = -1 + Math.random() * 2;
    let s = random();
    let radius = random() * hider_spawn_bounds;
    let theta = random() * Math.PI * 2;
    let x = radius * Math.cos(theta);
    let y = radius * Math.sin(theta);
    let rand_hider = new Hider(r,s,x,y);
    hiders.push(rand_hider);
}

function walk(i) {
    if(hiders.length == 0){
        return false;
    }
    for(let j in hunters) {
        if(draw_hunters == 1) {
            hunters[j].draw();
        }
        list_hiders = hunters[j].move(hunters, hiders);
    }
    for(let j in hiders) {
        if(draw_hiders == 1) {
            hiders[j].draw();           
        }
        hiders[j].move(hunters, hiders);
    }
    return i < ticks;
}