import processing.opengl.*; ArrayList people = new ArrayList(); ArrayList deaths = new ArrayList(); //Deaths this tick ArrayList births = new ArrayList(); ArrayList deadPeople = new ArrayList(); //All dead people int numPeople = 10; ArrayList buildings = new ArrayList(); int numBuildings = 20; Grid[][] grid; int gridW = 10; int W = 60; int H = 40; long tick = 1; long frame = 0; int ticksPerYear = 100; int textLine = 1; long nextPersonID = 1; boolean paused = false; boolean debug = false; class Person { PVector pos; int size = 1; long id; int health = 200; int age = 0; long birthday = 0; String name = ""; int thirst = 0; int hunger = 0; boolean female; int pregnant = -1; String causeOfDeath = null; ArrayList path = null; Person(long id, int x, int y, int age) { this.id = id; this.pos = new PVector(x, y); this.age = age; this.birthday = tick; this.female = random(1) > 0.5; makeNames(); } void makeNames() { name = makeName() + " " + makeName(); } String makeName() { String thisName = ""; char[] vowels = new char[]{'a', 'e', 'i', 'o', 'u', 'y'}; char[] consts = new char[]{'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v', 'w', 'y', 'z'}; int nameLen = 2+(int)random(5); for (int i = 0; i < nameLen; i++) { if (i%2==0) thisName += consts[(int)random(consts.length)]; else thisName += vowels[(int)random(vowels.length)]; if (i == 0) { thisName = thisName.toUpperCase(); } } return thisName; } void draw() { int drawSize = size*gridW; int offset = 0; if (age <= 15) { drawSize *= ((float)age+1)/16; drawSize += drawSize%2; offset = ((size*gridW)-drawSize)/2; } float r = 200, g = 150, b = 100; if (age >= 40) { r += 2*(age-40); g += 2*(age-40); b += 2*(age-40); if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; } fill(r,g,b); if (female) { ellipse(drawSize/2 + pos.x*gridW+offset, drawSize/2 + pos.y*gridW+offset, drawSize, drawSize); } else { rect(pos.x*gridW+offset, pos.y*gridW+offset, drawSize, drawSize); } if (path != null && debug) { for (Grid gr: path) { fill(100, 100, 100); rect(4+gr.pos.x*gridW, 4+gr.pos.y*gridW, 2, 2); } } } Grid getGrid() { return grid[(int)pos.x][(int)pos.y]; } void update() { if (tick != birthday && tick % ticksPerYear == birthday % ticksPerYear) { age++; } hunger++; thirst++; if (hunger >= 200) { health--; if (health == 0) causeOfDeath = "starvation"; } if (thirst >= 100) { //health--; } if (age >= 60) { if (random(1) < 0.1) health--; if (health == 0) causeOfDeath = "old age"; } if (health <= 0) { println(name + " died at age " + age + "."); getGrid().person = null; deaths.add(this); } if (pregnant > 0) { pregnant--; } if (pregnant == 0) { for (Grid g: getGrid().neighbors) { if (g.accessible()) { Person babbie = new Person(nextPersonID++, (int)g.pos.x, (int)g.pos.y, 1); births.add(babbie); g.person = babbie; pregnant = -1; break; } } } Building goTo = null; if (hunger >= 50 && path == null) { //Try to eat: boolean fed = false; for (Grid g: getGrid().neighbors) { if (g.building != null && g.building.food > 0) { hunger = 0; g.building.food--; g.building.diners.add(this); fed = true; break; } } if (!fed) { goTo = nearestBuildingWithFood(); if (goTo != null) { pathTo(goTo.grids.get(0)); } } } int newX = (int)pos.x, newY = (int)pos.y; if (path == null) { if (random(1) > 0.99) { newX++; } if (random(1) > 0.99) { newX--; } if (random(1) > 0.99) { newY++; } if (random(1) > 0.99) { newY--; } if (test(newX, newY)) { grid[(int)pos.x][(int)pos.y].person = null; pos.x = newX; pos.y = newY; grid[newX][newY].person = this; } } else { if (path.size() > 0) { Grid nextPos = path.remove(0); if (nextPos.accessible()) { grid[(int)pos.x][(int)pos.y].person = null; pos.x = nextPos.pos.x; pos.y = nextPos.pos.y; grid[(int)pos.x][(int)pos.y].person = this; } else { path = null; } } else { path = null; } } } boolean test(int newX, int newY) { if (newX < 0 || newX >= W) return false; if (newY < 0 || newY >= H) return false; if (grid[newX][newY].person != null) return false; if (grid[newX][newY].building != null) return false; return true; } Building nearestBuildingWithFood() { Building closest = null; float minDist = Integer.MAX_VALUE; for (Building building : buildings) { float distance = building.pos.dist(pos); if (distance < minDist && building.food > 0) { closest = building; minDist = distance; } } return closest; } void pathTo(Grid dest) { path = new ArrayList(); HashSet tried = new HashSet(); Grid start = grid[(int)pos.x][(int)pos.y]; Grid current = start; start.dist = start.pos.dist(dest.pos); //println("Finding path for: " + id); while (current != dest) { Grid closest = null; float minDist = Float.MAX_VALUE; for (Grid g: current.neighbors) { if (!g.accessible() && g != dest) continue; if (tried.contains(g)) continue; if (g.dist == 0) { g.dist = g.pos.dist(dest.pos); tried.add(closest); } if (g.dist < minDist) { closest = g; minDist = g.dist; } } if (closest == null) { //Pathfinding failed. println("Couldn't find a full path for: " + id); break; } //println("Going to: " + closest.pos); path.add(closest); if (path.size() > 20) { println("Couldn't find a full path for: " + id); break; } current = closest; } start.dist = 0; for (Grid g: path) { g.dist = 0; } } } class Building { int w, h; long id; int food = 1; PVector pos; ArrayList grids; HashSet diners = new HashSet(); Building(long id, int x, int y, int w, int h) { this.id = id; pos = new PVector(x, y); this.w = w; this.h = h; grids = new ArrayList(); } void draw() { fill(0,0,0,100); stroke(10,10,10); rect(pos.x*gridW, pos.y*gridW, w*gridW, h*gridW); for (int i = 0; i < food; i++) { Grid g = grids.get(i); fill(200, 200, 0); stroke(100, 100, 100); rect(g.pos.x*gridW+1, g.pos.y*gridW+6, 6, 3); } } void update() { if (diners.size() == 2) { Person p1 = null; Person p2 = null; for (Person diner: diners) { if (p1 == null) p1 = diner; else p2 = diner; } if (p1.age > 15 && p2.age > 15) { if (p1.female && !p2.female && p1.pregnant == -1) { p1.pregnant = 100; } else if (p2.female && !p1.female && p2.pregnant == -1) { p2.pregnant = 100; } } } if (tick % 50 == 0) { diners.clear(); } } } class Grid { PVector pos; Person person = null; Building building = null; //Cached distance during path finding. float dist = 0f; ArrayList neighbors = new ArrayList(); Grid(int x, int y) { pos = new PVector(x, y); } void fillNeighbors() { int x = (int)pos.x; int y = (int)pos.y; for (int i = x-1; i <= x+1; i++) { for (int j = y-1; j <= y+1; j++) { if (i < 0 || i >= W || j < 0 || j >= H || (i == x && j==y)) continue; neighbors.add(grid[i][j]); } } } boolean accessible() { return person == null && building == null; } void draw() { noFill(); stroke(100, 100, 100, 20); if (debug) { if (this.dist != 0) { fill(50, 50, 50, 100); } } rect(pos.x*gridW, pos.y*gridW, gridW, gridW); } } void setup() { frameRate(40); grid = new Grid[W][H]; buildings.clear(); people.clear(); deadPeople.clear(); deaths.clear(); births.clear(); for (int i = 0; i < W; i++) { for (int j = 0; j < H; j++) { grid[i][j] = new Grid(i, j); } } for (int i = 0; i < W; i++) { for (int j = 0; j < H; j++) { grid[i][j].fillNeighbors(); } } int i = 0; buildingLoop: while(i < numBuildings) { int w = 2+(int)random(3); int h = 2+(int)random(3); int x = (int)random(W-w); int y = (int)random(H-h); for (int j = x; j < x+w; j++) { for (int k = y; k < y+h; k++) { if (grid[j][k].building != null) { continue buildingLoop; } } } i++; Building building = new Building(i, x, y, w, h); buildings.add(building); for (int j = x; j < x+w; j++) { for (int k = y; k < y+h; k++) { grid[j][k].building = building; building.grids.add(grid[j][k]); } } } i = 0; while (i < numPeople) { int x = (int)random(W); int y = (int)random(H); if (grid[x][y].person != null) continue; if (grid[x][y].building != null) continue; Person person = new Person(nextPersonID++, x, y, 5+(int)random(60)); people.add(person); grid[x][y].person = person; i++; } textFont(createFont("sans-serif", 14)); noStroke(); //size(W*gridW, H*gridW + 200); size(600,600); } void update() { if (paused) return; tick++; for (Person person : people) { person.update(); } people.removeAll(deaths); deadPeople.addAll(deaths); deaths.clear(); people.addAll(births); births.clear(); for (Building building : buildings) { building.update(); } } void draw() { frame++; if (frame % 4 == 0) { update(); } textLine = 1; background(220,255,220); for (Person person : people) { person.draw(); } for (Building building : buildings) { building.draw(); } for (int i = 0; i < W; i++) { for (int j = 0; j < H; j++) { grid[i][j].draw(); } } fill(100); if (paused) {disp("Paused");} disp("Population: " + people.size()); disp("Year: " + tick/ticksPerYear); disp("Day: " + tick%ticksPerYear); if (mouseX/gridW < W && mouseY/gridW < H) { Grid mouseGrid = grid[mouseX/gridW][mouseY/gridW]; if (mouseGrid.person != null) { disp("Person: " + mouseGrid.person.name); disp(" Age: " + mouseGrid.person.age); disp(" Health: " + mouseGrid.person.health); disp(" Hunger: " + mouseGrid.person.hunger); disp(" Gender: " + (mouseGrid.person.female ? "Female" : "Male")); if (mouseGrid.person.pregnant >= 0) { disp(" Pregnant!"); } } if (mouseGrid.building != null) { disp("Building: " + mouseGrid.building.id); disp(" Food: "+ mouseGrid.building.food); if (mouseGrid.building.diners.size() > 0) { disp(" Diners: "); for (Person diner : mouseGrid.building.diners) { disp(" " + diner.name); } } } } for (int i = 0; i < deadPeople.size() && i < 20; i++) { Person p = deadPeople.get(deadPeople.size()-1-i); textAlign(RIGHT); String deathStr = p.name + " died of " + p.causeOfDeath + " at " + p.age + "."; text(deathStr, (W*gridW)-5, H*gridW+(i+1)*15); } } void mousePressed() { if (mouseX/gridW < W && mouseY/gridW < H) { Grid mouseGrid = grid[mouseX/gridW][mouseY/gridW]; Building b = mouseGrid.building; if (b != null) { if (b.food < b.w * b.h) { b.food++; } } if (mouseGrid.person != null) { mouseGrid.person.health = 0; mouseGrid.person.causeOfDeath = "smiting"; } } } void keyPressed() { if (key == 'p') { paused = !paused; } else if (key == 'r') { setup(); } else if (key == 'd') { debug = !debug; } } void disp(String s) { textAlign(LEFT); text(s, 0, H*gridW+textLine*15); textLine++; }