SOURCE

/*
 * Animated squares moving at right angles to the vertical and horizontal axes in an enclosed space
 * ------------------------------------------------------------------------------------------------
 * @author:    Caleb Nii Tetteh Tsuru Addy
 * @date:      19th June, 2020 
 * @email:     calebniitettehaddy@gmail.com 
 * @twitter:   @cnttaddy
 * @github :   https://github.com/niitettehtsuru/Enclosure
 * @codepen:   https://codepen.io/niitettehtsuru/pen/oNbWwRP
 * @license:   GNU General Public License v3.0 
 */

//A big block[The static blocks that enclose the moving blocks]
class BigBlock
{
    constructor(xCoord,yCoord,size,strokeColor,fillColor)
    {       
        this.xCoord = xCoord;
        this.yCoord = yCoord;  
        this.size = size;    
        this.strokeColor = strokeColor;  
        this.fillColor = 'maroon';//fillColor;  
        this.xCenter = this.xCoord + this.size/2; 
        this.yCenter = this.yCoord + this.size/2;    
    }  
    draw()//draw the big square
    {     
        stroke(this.strokeColor); 
        strokeWeight(1); 
        fill(this.fillColor);  
        rect(this.xCoord,this.yCoord,this.size,this.size);      
    }  
} 

//A small block[The blocks that move about in the grid]
class SmallBlock
{
    constructor(xCoord,yCoord,size,bigBlocks,fillColor)
    {       
        this.id = ~~((Math.random() * 100000000) + 1);; 
        this.xCoord = xCoord;
        this.yCoord = yCoord;  
        this.size = size;//length and breadth of the block. Since its a square, length is same as breadth.  
        this.fillColor = fillColor;      
        this.unitDistance = 4;//distance moved per animation frame   
        this.bigBlocks  = bigBlocks;
        this.smallBlocks = []; 
        this.speed   = this.setInitialSpeed(); 
        this.hasCollidedWithABigBlock = false;  
        this.hasCollidedWithAWall = false; 
    } 
    setSmallBlocks(smallBlocks)
    {
        this.smallBlocks = smallBlocks; 
    } 
    //Sets the speed at start. Block is set to only move perpendicular to the x-axis or the y-axis. 
    setInitialSpeed()
    {
        let x = 0;
        let y = 0;
        //flip a coin to decide if block moves horizontally or vertically 
        if(Math.random() > 0.5)//for horizontal movement    
        { 
            x = Math.random() > 0.5? -this.unitDistance: this.unitDistance;//flip a coin to decide if block moves left or right 
        }
        else//for vertical movement
        {
            y = Math.random() > 0.5? -this.unitDistance: this.unitDistance;//flip a coin to decide if block moves upwards or downwards
        } 
        return {x:x,y:y};  
    }
    //detects collision of this block with another block
    hasTouchedABlock(otherBlock)
    {
        let topEdgeOfThisBlock = this.yCoord + this.size;
        let rightEdgeOfThisBlock = this.xCoord + this.size; 
        let leftEdgeOfThisBlock = this.xCoord;
        let bottomEdgeOfThisBlock = this.yCoord;
        let topEdgeOfOtherBlock = otherBlock.yCoord + otherBlock.size;
        let rightEdgeOfOtherBlock = otherBlock.xCoord + otherBlock.size; 
        let leftEdgeOfOtherBlock = otherBlock.xCoord;
        let bottomEdgeOfOtherBlock = otherBlock.yCoord;  
        if
        ( 
            leftEdgeOfThisBlock < rightEdgeOfOtherBlock && 
            rightEdgeOfThisBlock > leftEdgeOfOtherBlock && 
            bottomEdgeOfThisBlock < topEdgeOfOtherBlock && 
            topEdgeOfThisBlock > bottomEdgeOfOtherBlock
        )
        {
            return true;//there is a collision 
        }
        return false;
    } 
    //determines the direction to move after this block collides on its top or bottom edge
    setDirectionAfterVerticalHit()
    {
        //after a bottom or up hit, block can either go in the opposite direction vertically, right or left.
        //flip a coin to decide
        if(Math.random() > 0.5)//for going in the opposite direction vertically 
        {
            this.speed.x = 0;
            this.speed.y = -this.speed.y; 
        }
        else//for going right or left
        {
            this.speed.x = Math.random() > 0.5? this.unitDistance: -this.unitDistance; 
            this.speed.y = 0; 
        }
    } 
    //determines the direction to move after this block collides on its right or left edge
    setDirectionAfterHorizontalHit()
    {
        //after a right or left hit, block can either go in the opposite direction horizontally, up or down 
        //flip a coin to decide
        if(Math.random() > 0.5)//for going in the opposite direction horizontally 
        {
            this.speed.x = -this.speed.x;
            this.speed.y = 0;
        }
        else//for going up or down
        {
            this.speed.x = 0;
            this.speed.y = Math.random() > 0.5? this.unitDistance: -this.unitDistance; 
        }
    }
    blockHitsLeftWall(horizontalOffset)
    {
        if(this.xCoord < horizontalOffset)
        {
            return true;
        }
        return false; 
    }
    blockHitsRightWall(width)
    {
        if(this.xCoord + this.size > width)
        {
            return true;
        }
        return false;
    }
    blockHitsTopWall(verticalOffset)
    {
        if(this.yCoord < verticalOffset)
        {
            return true;
        }
        return false; 
    }
    blockHitsBottomWall(height)
    {
        if(this.yCoord + this.size > height)
        {
            return true;
        }
        return false; 
    }   
    checkCollisionWithWall(width,horizontalOffset,verticalOffset,height)
    {
        if(this.blockHitsLeftWall(horizontalOffset))
        {
            this.xCoord = horizontalOffset;//ensure block never goes beyond the left edge 
            this.setDirectionAfterHorizontalHit();
            this.hasCollidedWithAWall = true;
        }
        else if(this.blockHitsRightWall(width - horizontalOffset))
        {
            this.xCoord = width - horizontalOffset - this.size-1;//ensure block never goes beyond the right edge 
            this.setDirectionAfterHorizontalHit(); 
            this.hasCollidedWithAWall = true;
        }
        else if(this.blockHitsTopWall(verticalOffset))
        {
            this.yCoord = verticalOffset+1;//ensure block never goes beyond the top edge 
            this.setDirectionAfterVerticalHit(); 
            this.hasCollidedWithAWall = true;
        }
        else if(this.blockHitsBottomWall(height - verticalOffset))
        {
            this.yCoord = height - verticalOffset - this.size-1;//ensure block never goes beyond the bottom edge 
            this.setDirectionAfterVerticalHit();
            this.hasCollidedWithAWall = true; 
        }  
    } 
    checkCollisionWithBigBlocks()
    {
        for(let i = 0; i < this.bigBlocks.length; i++)//check for collision with big blocks
        { 
            let block = this.bigBlocks[i]; 
            if(this.hasTouchedABlock(block))
            {   
                if(this.speed.x > 0)//if this block is moving right
                { 
                    
                    if(block.yCoord + block.size <= this.yCoord)//if big block is right above this block
                    {
                        continue;//ignore it
                    } 
                    this.xCoord = block.xCoord - this.size - 1;//back off slightly to the left
                    this.setDirectionAfterHorizontalHit();//change direction to go either right,up or down
                    this.hasCollidedWithABigBlock=true;
                    break; 
                }
                else if(this.speed.x < 0)//if this block is moving left
                {
                    if(block.yCoord + block.size <= this.yCoord)//if big block is right above this block
                    {
                        continue;//ignore it
                    } 
                    this.xCoord = block.xCoord + block.size + 1;
                    this.setDirectionAfterHorizontalHit(); 
                    this.hasCollidedWithABigBlock=true;
                    break; 
                }
                else if(this.speed.y > 0)//if this block is moving down
                {
                    
                    if(block.xCoord + block.size <= this.xCoord)//if big block is to the immediate left of this block
                    {
                        continue;//ignore it
                    }  
                    this.yCoord = block.yCoord - this.size - 1;
                    this.setDirectionAfterVerticalHit();
                    this.hasCollidedWithABigBlock=true;    
                    break; 
                }
                else if(this.speed.y < 0)//if this block is moving up
                { 
                    if(block.xCoord + block.size <= this.xCoord)//if big block is to the immediate left of this block
                    {
                        continue;//ignore it
                    }  
                    this.yCoord = block.yCoord + block.size + 1;
                    this.setDirectionAfterVerticalHit(); 
                    this.hasCollidedWithABigBlock=true;
                    break; 
                } 
            } 
        } 
    } 
    checkCollisionWithSmallBlocks()
    {
        for(let i = 0; i < this.smallBlocks.length; i++)//check for collision with other small blocks
        { 
            let block = this.smallBlocks[i]; 
            if(block.id !== this.id && this.hasTouchedABlock(block))
            {    
                if(this.speed.x > 0)//if this block is moving right
                {  
                    if(block.yCoord + block.size <= this.yCoord)//if other block is right above this block
                    {
                        continue;//ignore
                    }  
                    this.xCoord = block.xCoord - this.size - 1;//back off slightly to the left 
                    this.setDirectionAfterHorizontalHit();  
                    break;
                }
                else if(this.speed.x < 0)//if this block is moving left
                { 
                    if(block.yCoord + block.size <= this.yCoord)//if other block is right above this block
                    {
                        continue;//ignore
                    } 
                    this.xCoord = block.xCoord + block.size + 1;//back off slightly to the right
                    this.setDirectionAfterHorizontalHit();  
                    break; 
                }
                else if(this.speed.y > 0)//if this block is moving down
                { 
                    if(block.xCoord + block.size <= this.xCoord)//if other block is to the immediate left of this block
                    {
                        continue;//ignore
                    } 
                    this.yCoord = block.yCoord - this.size - 1;//back off slightly to the top
                    this.setDirectionAfterVerticalHit();  
                    break;
                }
                else if(this.speed.y < 0)//if this block is moving up
                {
                    if(block.xCoord + block.size <= this.xCoord)//if other block is to the immediate left of this block
                    {
                        continue;//ignore
                    }
                    this.yCoord = block.yCoord + block.size + 1;//back off slightly to the bottom 
                    this.setDirectionAfterVerticalHit();  
                    break; 
                } 
            } 
        } 
    } 
    update(width,horizontalOffset,height,verticalOffset)
    {     
        this.hasCollidedWithABigBlock=false; 
        this.hasCollidedWithAWall = false;
        //keep the block moving in its current direction 
        this.xCoord += this.speed.x;//if block is going left or right, keep it going
        this.yCoord += this.speed.y;//if block is going up or down, keep it going   
        
        this.checkCollisionWithBigBlocks(); 
        if(!this.hasCollidedWithABigBlock)
        {
            this.checkCollisionWithWall(width,horizontalOffset,verticalOffset,height); 
            if(!this.hasCollidedWithAWall)
            {
                this.checkCollisionWithSmallBlocks();
            }  
        }   
    }
    draw()//draw the small block 
    {     
        fill(this.fillColor);  
        rect(this.xCoord,this.yCoord,this.size,this.size);     
    } 
} 

//Sets everything up
let bigBlocks = [];
let smallBlocks = []; 
let blockLength = 50;//length of a side of a big block  
let horizontalOffset = 0; 
let verticalOffset = 0;
let backgroundColor = 'rgba(0,0,0,0.05)';//black 
//get the upper left vertex of each square to be created 
function getBlockVertices(blockLength,windowSize)//divide the canvas into blocks 
{
    let 
    bigBlockVertices = [],
    smallBlockVertices = [];  
    //How many blocks can be set on the canvas horizontally?
    let numHorizontal = ~~(windowSize.width/blockLength);//num of blocks that can be packed horizontally
    let horizontalRemainder = windowSize.width - blockLength * numHorizontal;//the space left when all blocks are packed
    horizontalOffset = horizontalRemainder/2;//so an equal space is at the left and right of the grid
    //How many blocks can be set on the canvas vertically? 
    let numVertical = ~~(windowSize.height/blockLength);//num of blocks that can be packed vertically
    let verticalRemainder = windowSize.height - blockLength * numVertical;//the space left when all blocks are packed  
    verticalOffset = verticalRemainder/2;//so an equal space is at the top and bottom of the grid 
    for(let y = verticalOffset; y < windowSize.height; y+=blockLength)//get all points in the grid, starting from the top to the bottom
    { 
        if(y+ blockLength > windowSize.height)//if the next point is beyond the bottom edge of the canvas
        {
            continue; //skip
        } 
        for(let x = horizontalOffset; x < windowSize.width; x+=blockLength)//all the while, getting all the horizontal points at each level 
        { 
            if(x+blockLength > windowSize.width)//if the next point is beyond the right edge of the canvas
            { 
                continue; //skip
            } 
            //flip a coin to add or reject the vertex
            if( Math.random() > 0.5)//if vertex is accepted
            {
                bigBlockVertices.push({x:x,y:y,size:blockLength});
            }
            else//if vertex is rejected (will result in an empty space)
            {
                if(Math.random() > 0.5)//flip a coin to add or reject a small block
                {
                    smallBlockVertices.push({x:x,y:y});
                } 
            } 
        } 
    }
    return {bigBlocks:bigBlockVertices,smallBlocks:smallBlockVertices}; 
}  
//get the width and height of the browser window 
function getBrowserWindowSize() 
{
    let win = window,
    doc = document,
    offset = 20,
    docElem = doc.documentElement,
    body = doc.getElementsByTagName('body')[0],
    browserWindowWidth = win.innerWidth || docElem.clientWidth || body.clientWidth,
    browserWindowHeight = win.innerHeight|| docElem.clientHeight|| body.clientHeight;  
    return {width:browserWindowWidth-offset,height:browserWindowHeight-offset}; 
} 
function setNewGrid() 
{
    let browserWindowSize = getBrowserWindowSize();  
    //create a new grid with new blocks
    let vertices = getBlockVertices(blockLength,browserWindowSize);  
    let bigBlockVertices = vertices.bigBlocks;//coordinates of the upper left vertices of the large blocks
    let smallBlockVertices = vertices.smallBlocks;//coordinates of the upper left vertices of the large small
    bigBlocks = [];//empty it
    bigBlockVertices.forEach(function(vertex)//create big blocks
    { 
        bigBlocks.push(new BigBlock(vertex.x,vertex.y,blockLength,255,backgroundColor));
    });
    smallBlocks = [];//empty it 
    smallBlockVertices.forEach(function(vertex)//create small blocks
    {    
        let divisor  = Math.random() > 0.5? 2.1:4;
        smallBlocks.push(new SmallBlock(vertex.x,vertex.y,blockLength/divisor,bigBlocks,backgroundColor));
    }); 
    smallBlocks.forEach(function(smallBlock)
    {   
        smallBlock.setSmallBlocks(smallBlocks);  
    });
}
function setup() 
{
    let browserWindowSize = getBrowserWindowSize();  
    createCanvas(browserWindowSize.width,browserWindowSize.height); 
    let vertices = getBlockVertices(blockLength,browserWindowSize);  
    let bigBlockVertices = vertices.bigBlocks;//coordinates of the upper left vertices of the large blocks
    let smallBlockVertices = vertices.smallBlocks;//coordinates of the upper left vertices of the large small
    bigBlockVertices.forEach(function(vertex)//create big blocks
    { 
        bigBlocks.push(new BigBlock(vertex.x,vertex.y,blockLength,255,backgroundColor));
    });
    smallBlockVertices.forEach(function(vertex)//create small blocks
    {    
        let divisor  = Math.random() > 0.5? 2.1:4;
        smallBlocks.push(new SmallBlock(vertex.x,vertex.y,blockLength/divisor,bigBlocks,backgroundColor));
    }); 
    smallBlocks.forEach(function(smallBlock)
    {   
        smallBlock.setSmallBlocks(smallBlocks);  
    });
    background(backgroundColor); 
    document.addEventListener('click',(event)=>//when user clicks on the canvas,
    {    
        setNewGrid();
    });
    window.addEventListener('resize',function()
    {
        
        let browserWindowSize = getBrowserWindowSize(); 
        resizeCanvas(browserWindowSize.width,browserWindowSize.height); 
        setNewGrid();
        background(backgroundColor);
    });
} 
function draw() 
{  
    background(backgroundColor); 
    [...bigBlocks,...smallBlocks].forEach(function(block)//draw all blocks
    {  
        block.draw(); 
    }); 
    smallBlocks.forEach(function(block)//update small blocks
    { 
        block.update(width,horizontalOffset,height,verticalOffset); 
    }); 
}
console 命令行工具 X clear

                    
>
console