basic evolution finished

This commit is contained in:
MaxRobinsonTheGreat
2020-07-04 14:01:30 -06:00
parent 8d208ca9cc
commit 9b24e4cc1c
16 changed files with 2878 additions and 7 deletions

200
dist/bundle.js vendored Normal file

File diff suppressed because one or more lines are too long

14
dist/css/style.css vendored Normal file
View File

@@ -0,0 +1,14 @@
body{
background: #121D29;
margin: 0;
padding: 0;
}
canvas {
display: block;
}
* {
margin: 0;
padding: 0;
}

12
dist/html/index.html vendored Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charste="UTF-8">
<title>Evolution Simulator</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<canvas id='canvas'></canvas>
<script src="../bundle.js"></script>
</body>
</html>

1935
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,10 @@
"name": "EvolutionSimulatorV2",
"version": "1.0.0",
"description": "Version 2 of the evolution simulator",
"main": "index.js",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode=production",
"build": "webpack --mode=production",
"build-dev": "webpack --mode=development",
"build-watch": "webpack --watch --mode=development"
},
@@ -21,6 +21,8 @@
},
"homepage": "https://github.com/MaxRobinsonTheGreat/EvolutionSimulatorV2#readme",
"devDependencies": {
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12"
}
},
"dependencies": {}
}

94
src/Cell.js Normal file
View File

@@ -0,0 +1,94 @@
const CellTypes = require("./CellTypes");
// A cell exists in a grid system.
class Cell{
constructor(type, col, row, x, y){
this.owner = null;
this.setType(type);
this.col = col;
this.row = row;
this.x = x;
this.y = y;
}
setType(type) {
this.type = type;
}
performFunction(env) {
switch(this.type){
case CellTypes.mouth:
eatFood(this, env);
break;
case CellTypes.producer:
growFood(this, env);
break;
case CellTypes.killer:
killNeighbors(this, env);
break;
}
}
getColor() {
return CellTypes.colors[this.type];
}
isLiving() {
return this.type != CellTypes.empty &&
this.type != CellTypes.food &&
this.type != CellTypes.wall;
}
}
function eatFood(self, env){
eatNeighborFood(env.grid_map.cellAt(self.col+1, self.row), self, env);
eatNeighborFood(env.grid_map.cellAt(self.col-1, self.row), self, env);
eatNeighborFood(env.grid_map.cellAt(self.col, self.row+1), self, env);
eatNeighborFood(env.grid_map.cellAt(self.col, self.row-1), self, env);
}
function eatNeighborFood(n_cell, self, env){
if (n_cell == null)
return;
if (n_cell.type == CellTypes.food){
env.changeCell(n_cell.col, n_cell.row, CellTypes.empty, null);
self.owner.food_collected++;
}
}
function growFood(self, env){
if (self.owner.is_mover)
return;
for (var c=-1; c<=1; c++){
for (var r=-1; r<=1; r++){
if (r==0 && c==0)
continue;
var cell = env.grid_map.cellAt(self.col+c, self.row+r);
if (cell != null && cell.type == CellTypes.empty && Math.random() * 100 <= 0.5){
env.changeCell(self.col+c, self.row+r, CellTypes.food, null);
return;
}
}
}
}
function killNeighbors(self, env) {
killNeighbor(env.grid_map.cellAt(self.col+1, self.row));
killNeighbor(env.grid_map.cellAt(self.col-1, self.row));
killNeighbor(env.grid_map.cellAt(self.col, self.row+1));
killNeighbor(env.grid_map.cellAt(self.col, self.row-1));
}
function killNeighbor(n_cell) {
if(n_cell == null) {
return
}
if (n_cell.type != CellTypes.armor && n_cell.owner != null && n_cell.owner != self.owner && n_cell.owner.living){
n_cell.owner.die();
if (n_cell == CellTypes.killer){
self.owner.die();
}
}
}
module.exports = Cell;

16
src/CellTypes.js Normal file
View File

@@ -0,0 +1,16 @@
const CellTypes = {
empty: 0,
food: 1,
wall: 2,
mouth: 3,
producer: 4,
mover: 5,
killer: 6,
armor: 7,
colors: ['#121D29', 'green', 'black', 'orange', 'white', 'blue', 'red', 'purple'],
getRandomLivingType: function(){
return Math.floor(Math.random() * 5) + 3;
}
}
module.exports = CellTypes;

34
src/Engine.js Normal file
View File

@@ -0,0 +1,34 @@
const Environment = require('./Environment');
class Engine{
constructor(){
this.fps = 0;
this.environment = new Environment(5);
this.environment.OriginOfLife();
this.last_update = Date.now();
this.delta_time = 0;
this.running = false;
}
start(fps=60) {
this.fps = fps;
this.game_loop = setInterval(function(){this.update();}.bind(this), 1000/fps);
this.runnning = true;
}
stop() {
clearInterval(this.game_loop);
this.running = false;
}
update() {
this.delta_time = Date.now() - this.last_update;
this.last_update = Date.now();
this.environment.update(this.delta_time);
this.environment.render();
}
}
module.exports = Engine;

72
src/Environment.js Normal file
View File

@@ -0,0 +1,72 @@
const Grid = require('./GridMap');
const Renderer = require('./Rendering/Renderer');
const GridMap = require('./GridMap');
const Organism = require('./Organism');
const CellTypes = require('./CellTypes');
const Cell = require('./Cell');
const EnvironmentController = require('./EnvironmentController');
class Environment{
constructor(cell_size) {
this.renderer = new Renderer('canvas', this, cell_size);
this.controller = new EnvironmentController(this, this.renderer.canvas);
this.grid_rows = Math.floor(this.renderer.height / cell_size);
this.grid_cols = Math.floor(this.renderer.width / cell_size);
this.grid_map = new GridMap(this.grid_cols, this.grid_rows, cell_size);
this.renderer.renderFullGrid();
this.organisms = [];
}
// {
// running: 0,
// paused: 1,
// headless: 2
// };
update(delta_time) {
var to_remove = [];
for (var i in this.organisms) {
var org = this.organisms[i];
if (!org.update()) {
to_remove.push(i);
}
}
this.removeOrganisms(to_remove);
}
render() {
// console.log(this.cells_to_render);
this.renderer.renderCells();
this.renderer.renderHighlights();
}
removeOrganisms(org_indeces) {
for (var i of org_indeces.reverse()){
this.organisms.splice(i, 1);
}
}
OriginOfLife() {
var center = this.grid_map.getCenter();
var org = new Organism(center[0], center[1], this);
org.addCell(CellTypes.mouth, -1, -1);
org.addCell(CellTypes.producer, 0, 0);
org.addCell(CellTypes.mouth, 1, 1);
this.addOrganism(org);
}
addOrganism(organism) {
organism.updateGrid();
this.organisms.push(organism);
}
changeCell(c, r, type, owner) {
this.grid_map.setCellType(c, r, type);
this.grid_map.setCellOwner(c, r, owner);
this.renderer.addToRender(this.grid_map.cellAt(c, r));
}
}
module.exports = Environment;

View File

@@ -0,0 +1,52 @@
class EnvironmentController{
constructor(env, canvas) {
this.env = env;
this.canvas = canvas;
this.mouse_x;
this.mouse_y;
this.mouse_c;
this.mouse_r;
this.mouse_down = false;
this.cur_cell = null;
this.cur_org = null;
this.defineEvents();
}
defineEvents() {
this.canvas.addEventListener('mousedown', e => {
this.mouse_down=true;
});
this.canvas.addEventListener('mouseup', e => {
this.mouse_down=false;
});
this.canvas.addEventListener('mousemove', e => {
var prev_cell = this.cur_cell;
var prev_org = this.cur_org;
this.mouse_x = e.offsetX;
this.mouse_y = e.offsetY;
var colRow = this.env.grid_map.xyToColRow(this.mouse_x, this.mouse_y);
this.mouse_c = colRow[0];
this.mouse_r = colRow[1];
this.cur_cell = this.env.grid_map.cellAt(this.mouse_c, this.mouse_r);
this.cur_org = this.cur_cell.owner;
if (this.cur_org != prev_org || this.cur_cell != prev_cell) {
this.env.renderer.clearAllHighlights(true);
if (this.cur_org != null) {
this.env.renderer.highlightOrganism(this.cur_org);
}
else if (this.cur_cell != null) {
this.env.renderer.highlightCell(this.cur_cell, true);
}
}
});
}
}
module.exports = EnvironmentController;

73
src/GridMap.js Normal file
View File

@@ -0,0 +1,73 @@
const Cell = require('./Cell');
const CellTypes = require('./CellTypes');
class GridMap {
constructor(cols, rows, cell_size, filltype=CellTypes.empty) {
this.grid = [];
this.cols = cols;
this.rows = rows;
this.cell_size = cell_size;
for(var c=0; c<cols; c++) {
var row = [];
for(var r=0; r<rows; r++) {
var cell = new Cell(filltype, c, r, c*cell_size, r*cell_size);
row.push(cell);
}
this.grid.push(row);
}
}
fillGrid(type) {
for (var col of grid) {
for (var cell of col){
cell.setType(type);
}
}
}
cellAt(col, row) {
if (!this.isValidLoc(col, row)) {
return null;
}
return this.grid[col][row];
}
setCellType(col, row, type) {
if (!this.isValidLoc(col, row)) {
return;
}
this.grid[col][row].setType(type);
}
setCellOwner(col, row, owner) {
if (!this.isValidLoc(col, row)) {
return;
}
this.grid[col][row].owner = owner;
}
isValidLoc(col, row){
return col<this.cols && row<this.rows && col>=0 && row>=0;
}
getCenter(){
return [Math.floor(this.cols/2), Math.floor(this.rows/2)]
}
xyToColRow(x, y) {
var c = Math.floor(x/this.cell_size);
var r = Math.floor(y/this.cell_size);
if (c >= this.cols)
c = this.cols-1;
else if (c < 0)
c = 0;
if (r >= this.rows)
r = this.rows-1;
else if (r < 0)
r = 0;
return [c, r];
}
}
module.exports = GridMap;

12
src/LocalCell.js Normal file
View File

@@ -0,0 +1,12 @@
const CellTypes = require("./CellTypes");
// A local cell is a lightweight container for a cell in an organism. It does not directly exist in the grid
class LocalCell{
constructor(type, loc_col, loc_row){
this.type = type;
this.loc_col = loc_col;
this.loc_row = loc_row;
}
}
module.exports = LocalCell;

249
src/Organism.js Normal file
View File

@@ -0,0 +1,249 @@
const CellTypes = require("./CellTypes");
const Cell = require("./Cell");
const GridMap = require("./GridMap");
const LocalCell = require("./LocalCell");
const { producer } = require("./CellTypes");
class Organism {
constructor(col, row, env, parent=null) {
this.c = col;
this.r = row;
this.env = env;
this.lifetime = 0;
this.food_collected = 0;
this.living = true;
this.cells = [];
this.is_producer = false;
this.is_mover = false;
if (parent != null) {
this.inherit(parent);
}
}
addCell(type, c, r) {
for (var cell of this.cells) {
if (cell.loc_col == c && cell.loc_row == r)
return false;
}
this.checkProducerMover(type);
this.cells.push(new LocalCell(type, c, r));
return true;
}
removeCell(c, r) {
var check_change = false;
for (var i=0; i<this.cells.length; i++) {
var cell = this.cells[i];
if (cell.loc_col == c && cell.loc_row == r){
if (cell.type == this.producer || cell.type == this.mover) {
check_change = true;
}
this.cells.splice(i, 1);
break;
}
}
if (check_change) {
this.is_producer = false;
this.is_producer = false;
for (var cell of this.cells) {
this.checkProducerMover(cell.type);
}
}
}
checkProducerMover(type) {
if (type == CellTypes.producer)
this.is_producer = true;
if (type == CellTypes.mover)
this.is_mover = true;
}
inherit(parent) {
for (var c of parent.cells){
//deep copy parent cells
this.addCell(c.type, c.loc_col, c.loc_row);
}
}
// amount of food required before it can reproduce
foodNeeded() {
return this.cells.length;
}
lifespan() {
return this.cells.length * 150;
}
reproduce() {
//produce mutated child
//check nearby locations (is there room and a direct path)
var org = new Organism(0, 0, this.env, this);
if (Math.random() * 100 <= 5) {
org.mutate();
}
var direction = this.getRandomDirection();
var direction_c = direction[0];
var direction_r = direction[1];
var boost = Math.floor(Math.random() * 2) + 1;
boost = 1;
var new_c = this.c + (direction_c*this.cells.length*2) + (direction_c*boost);
var new_r = this.r + (direction_r*this.cells.length*2) + (direction_r*boost);
if (org.isClear(new_c, new_r)){// && org.isStraightPath(new_c, new_r, this.c, this.r, this)){
org.c = new_c;
org.r = new_r;
this.env.addOrganism(org);
org.updateGrid();
}
this.food_collected -= this.foodNeeded();
}
mutate() {
var choice = Math.floor(Math.random() * 3);
if (choice == 0) {
var type = CellTypes.getRandomLivingType();
var branch = this.cells[Math.floor(Math.random() * this.cells.length)];
var c = branch.loc_col+Math.floor(Math.random() * 2) - 1;
var r = branch.loc_row+Math.floor(Math.random() * 2) - 1;
return this.addCell(type, c, r);
}
else if (choice == 1){
// change cell
var cell = this.cells[Math.floor(Math.random() * this.cells.length)];
cell.type = CellTypes.getRandomLivingType();
this.checkProducerMover(cell.type);
return true;
}
else {
// remove cell
if(this.cells.length > 1) {
this.cells.splice(Math.floor(Math.random() * this.cells.length), 1);
return true;
}
}
return false;
}
attemptMove(col, row) {
var direction = this.getRandomDirection();
var direction_c = direction[0];
var direction_r = direction[1];
var new_c = this.c + direction_c;
var new_r = this.r + direction_r;
if (this.isClear(new_c, new_r)) {
for (var cell of this.cells) {
var real_c = this.c + cell.loc_col;
var real_r = this.r + cell.loc_row;
this.env.changeCell(real_c, real_r, CellTypes.empty, null);
}
this.c = new_c;
this.r = new_r;
this.updateGrid();
}
}
getRandomDirection(){
var directions = [[0,1],[0,-1],[1,0],[-1,0]]
return directions[Math.floor(Math.random() * directions.length)];
}
// assumes either c1==c2 or r1==r2, returns true if there is a clear path from point a to b
isStraightPath(c1, r1, c2, r2, parent){
// TODO FIX!!!
if (c1 == c2) {
if (r1 > r2){
var temp = r2;
r2 = r1;
r1 = temp;
}
for (var i=r1; i!=r2; i++) {
var cell = this.env.grid_map.cellAt(c1, i)
if (!this.isPassableCell(cell, parent)){
return false;
}
}
return true;
}
else {
if (c1 > c2){
var temp = c2;
c2 = c1;
c1 = temp;
}
for (var i=c1; i!=c2; i++) {
var cell = this.env.grid_map.cellAt(i, r1);
if (!this.isPassableCell(cell, parent)){
return false;
}
}
return true;
}
}
isPassableCell(cell, parent){
return cell.type == CellTypes.empty || cell.owner == this || cell.owner == parent;
}
isClear(col, row) {
for(var loccell of this.cells) {
var cell = this.getRealCell(loccell, col, row);
if(cell == null || cell.type != CellTypes.empty && cell.owner != this) {
return false;
}
}
return true;
}
die() {
for (var cell of this.cells) {
var real_c = this.c + cell.loc_col;
var real_r = this.r + cell.loc_row;
this.env.changeCell(real_c, real_r, CellTypes.food, null);
}
this.living = false;
}
updateGrid() {
for (var cell of this.cells) {
var real_c = this.c + cell.loc_col;
var real_r = this.r + cell.loc_row;
this.env.changeCell(real_c, real_r, cell.type, this);
}
}
update() {
// this.food_collected++;
this.lifetime++;
if (this.lifetime > this.lifespan()) {
this.die();
return this.living;
}
for (var cell of this.cells) {
this.getRealCell(cell).performFunction(this.env);
}
if (!this.living){
return this.living
}
if (this.is_mover) {
this.attemptMove();
}
if (this.food_collected >= this.foodNeeded()) {
this.reproduce();
}
return this.living;
}
getRealCell(local_cell, c=this.c, r=this.r){
var real_c = c + local_cell.loc_col;
var real_r = r + local_cell.loc_row;
return this.env.grid_map.cellAt(real_c, real_r);
}
}
module.exports = Organism;

103
src/Rendering/Renderer.js Normal file
View File

@@ -0,0 +1,103 @@
// Renderer controls access to a canvas. There is one renderer for each canvas
class Renderer {
constructor(canvas_id, env, cell_size) {
this.cell_size = cell_size;
this.env = env;
this.canvas = document.getElementById(canvas_id);
this.ctx = canvas.getContext("2d");
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
this.height = canvas.height;
this.width = canvas.width;
this.cells_to_render = new Set();
this.cells_to_highlight = new Set();
this.highlighted_cells = new Set();
}
clear() {
this.ctx.fillStyle = 'white';
this.ctx.fillRect(0, 0, this.height, this.width);
}
renderFullGrid() {
var grid = this.env.grid_map.grid;
for (var col of grid) {
for (var cell of col){
this.ctx.fillStyle = cell.getColor();
this.ctx.fillRect(cell.x, cell.y, this.cell_size, this.cell_size);
}
}
}
renderCells() {
for (var cell of this.cells_to_render) {
this.renderCell(cell);
}
this.cells_to_render.clear();
}
renderCell(cell) {
this.ctx.fillStyle = cell.getColor();
this.ctx.fillRect(cell.x, cell.y, this.cell_size, this.cell_size);
}
renderOrganism(org) {
for(var org_cell of org.cells) {
var cell = org.getRealCell(org_cell);
this.renderCell(cell);
}
}
addToRender(cell) {
if (this.highlighted_cells.has(cell)){
this.cells_to_highlight.add(cell);
}
this.cells_to_render.add(cell);
}
renderHighlights() {
for (var cell of this.cells_to_highlight) {
this.renderCellHighlight(cell);
this.highlighted_cells.add(cell);
}
this.cells_to_highlight.clear();
}
highlightOrganism(org) {
for(var org_cell of org.cells) {
var cell = org.getRealCell(org_cell);
this.cells_to_highlight.add(cell);
}
}
highlightCell(cell) {
this.cells_to_highlight.add(cell);
}
renderCellHighlight(cell, color="yellow") {
this.renderCell(cell);
this.ctx.fillStyle = color;
this.ctx.globalAlpha = 0.5;
this.ctx.fillRect(cell.x, cell.y, this.cell_size, this.cell_size);
this.ctx.globalAlpha = 1;
this.highlighted_cells.add(cell);
}
clearAllHighlights(clear_to_highlight=false) {
for (var cell of this.highlighted_cells) {
this.renderCell(cell);
}
this.highlighted_cells.clear();
if (clear_to_highlight) {
this.cells_to_highlight.clear();
}
}
}
// $("body").mousemove(function(e) {
// console.log("hello");
// });
module.exports = Renderer;

View File

@@ -0,0 +1,6 @@
'user strict';
import Engine from './Engine';
var engine = new Engine();
engine.start(45);

View File

@@ -3,7 +3,10 @@ const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
externals: {
jquery: 'jQuery'
}
};