// Galactic civilisation simulator // (c) 2000-2001 D. Clayton. All rights reserved. // Permission is granted to use this code if it remains // in its exact original form throughout. import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.util.*; import java.math.*; public class GalSim extends JFrame implements ActionListener { // initial parameters final int galw = 800, galh = 600, MAX_TYPES = 5, MAX_COL_DIST = 32, EVOLVE = 250, TECH_MAX = 10000000, MILLION = 1000000, PARANOIA_FACTOR = 50, MIN_CIVIL_WAR_POP = 500000, EVOLVE_2 = 10000000, MAX_RACES = 6, RAISE_IQ = 1200000, MAX_IQ = 300; int nstars = 20000, year = 2000, evolve = EVOLVE, initRaceNum = 3; PlayArea pa; Star [][]starmap; Vector races; boolean paused = false; public static void main(String args[]) { new GalSim(); } public GalSim() { Container c = getContentPane(); setSize(800,700); setResizable(false); c.setLayout(new BorderLayout()); pa = new PlayArea(); c.add(pa, "Center"); JPanel top = new JPanel(); JButton exit = new JButton("Exit"); exit.addActionListener(this); top.add(exit); JButton stats = new JButton("Stats"); stats.addActionListener(this); top.add(stats); c.add(top, "North"); System.out.println("Generating galaxy..."); generateGalaxy(); System.out.println("Creating initial races..."); for (int n = 0; n < initRaceNum; n++) { Race newRace = new Race(number(0, 2), number(50, 100), n); newRace.calcExpansionPolicy(); races.addElement(newRace); newRace.seed(); } addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); setVisible(true); // main loop while (true) { while (paused) {}; year++; repaint(); evolve--; if (evolve == 0) { for (int n = 0; n < races.size(); n++) { // weed out RIP'd races // not quite sure how we're going to do this yet :) } if (races.size() < MAX_RACES) { System.out.println("Evolution: creating new race..."); Race r = new Race(number(0, 3), number(50, 100), races.size()); r.calcExpansionPolicy(); races.addElement(r); r.seed(); } evolve = EVOLVE; } for (int n = 0; n < races.size(); n++) { Race r = (Race)races.elementAt(n); if (r.pop == 0) { r.RIP = true; } if (!r.RIP) { // basic logistics r.decayWars(); r.techAdvance(); r.age++; // alliances r.checkAlliances(); r.benefitAlliances(); if (r.type == 2) { // erratic r.paranoiaRoll(); } if (r.type == 0) { // warlike r.civilWarRoll(); } } } for (int y = 0; y < galh; y++) { for (int x = 0; x < galw; x++) { Star s = starmap[x][y]; if (s != null && s.pop > 0) { s.age++; Race ir = s.race; if (ir.tech >= EVOLVE_2) { ir.pop -= s.pop/2; s.pop -= s.pop/2; if (s.pop < 1000) { ir.pop -= s.pop; s.disInhabit(); if (ir.pop <= 0) { ir.RIP = true; } } } else { if (ir.civilWar) { if (s.age < 15 && number(0, ir.iq) < 15) { // gah! doh! :( int to = number(0, races.size()); Race tr = (Race)races.elementAt(to); tr.pop+=s.pop; ir.pop-=s.pop; s.race = tr; if (tr.tech < ir.tech) { tr.tech += ir.tech/100; } } } else { // do normal stuff ;) s.grow(); s.colonise(); } } } } } } } public void generateGalaxy() { // first generate stars starmap = new Star[galw][galh]; for (int y = 0; y < galh; y++) { for (int x = 0; x < galw; x++) { starmap[x][y] = null; } } for (int i = 0; i < nstars; i++) { int px = number(0, galw-1); int py = number(0, galh-1); while (starmap[px][py] != null) { px = number(0, galw-1); py = number(0, galh-1); } Star s = new Star(px, py, number(0, MAX_TYPES+1)); starmap[px][py] = s; } races = new Vector(); } public int number(int min, int max) { max -= min; return ((int) (Math.random() * max)) + min; } public long longNumber(long min, long max) { max -= min; return ((long) (Math.random() * max)) + min; } public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("Exit")) { System.exit(0); } else if (e.getActionCommand().equals("Stats")) { paused = true; new StatsWindow(); } } public class StatsWindow extends JFrame { public StatsWindow() { Container c = getContentPane(); setSize(640,550); setVisible(true); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { paused = false; dispose(); } }); } public void paint(Graphics g) { g.drawString("Status summary for the year "+year, 150, 45); for (int n = 0; n < races.size(); n++) { String typeS=null; Race r = (Race)races.elementAt(n); if (r.RIP) { g.setColor(Color.red); g.drawString("REST IN PEACE RACE "+n, 10, 70+n*70); } else { if (r.type == 0) { typeS = "warlike"; } else if (r.type == 1) { typeS = "peaceful"; } else if (r.type == 2) { typeS = "erratic"; } g.setColor(r.colorOld); g.drawString("Race "+n+": "+typeS+", base IQ: "+r.iq, 10, 60+n*70); g.setColor(Color.black); String techLevS="(interstellar)"; if (r.tech >= 100) { techLevS="(interstellar II)"; } if (r.tech >= 1000) { techLevS="(interstellar III)"; } if (r.tech >= 10000) { techLevS="(interstellar IV)"; } if (r.tech >= 100000) { techLevS="(interstellar V)"; } if (r.tech >= 1000000) { techLevS="(interstellar VI)"; } if (r.tech > RAISE_IQ) { techLevS="(geneering)"; } if (r.tech > EVOLVE_2) { techLevS="(die back)"; } g.drawString("Pop: "+r.pop+" Tech: "+r.tech+" "+techLevS, 10, 75+n*70); String alliesS = "Allies: "; for (int i = 0; i < races.size(); i++) { if (r.allies[i] != -1) { alliesS += "Race "+i+" "; } } g.drawString(alliesS, 10, 90+n*70); String warS = "Wars: "; for (int i = 0; i < races.size(); i++) { if (r.wars[i] != -1) { warS += "Race "+i+" ("+r.wars[i]+") "; } } if (r.civilWar) { warS += "At civil war! ("+r.civilWarPeriod+")"; } g.drawString(warS, 10, 105+n*70); } } } } public class Star { int x, y, type, age; Race race; long pop; Color starColor; public Star(int xx, int yy, int t) { x = xx; y = yy; type = t; race = null; age = 0; pop = 0; if (type == 0) { starColor = new Color(255, 255, 0); } else if (type == 1) { starColor = new Color(128, 128, 255); } else if (type == 2) { starColor = new Color(112, 64, 0); } else if (type == 3) { starColor = new Color(96, 0, 0); } else if (type == 4) { starColor = new Color(196, 196, 255); } else if (type == 5) { starColor = new Color(64, 64, 64); } } public void colonise() { if (pop < 50) return; int ab=0; for (int i = 0; i < MAX_RACES; i++) { if (race.wars[i] != -1) { ab+=20; } } if (age > 75+ab) return; Star targ = search(); if (targ == null) return; boolean stalled = false; if (targ.pop > 0) { // fight for it!! ;) if (race.type == 0 && longNumber(0, targ.race.pop) > longNumber(0, race.pop * 5)) { stalled = true; } if (race.type == 1 && longNumber(0, targ.race.pop) > longNumber(0, race.pop)) { stalled = true; } if (race.type == 2 && longNumber(0, targ.race.pop) > longNumber(0, race.pop * 2)) { stalled = true; } if (stalled) return; boolean won = battle(targ); if (won) { targ.race.pop -= targ.pop; if (targ.age > 50) { // core system! targ.race.allies[race.id] = -1; targ.race.wars[race.id] = 20 + (targ.race.type == 0 ? 15 : 0); race.allies[targ.race.id] = -1; race.wars[targ.race.id] = 20 + (targ.race.type == 0 ? 15 : 0); race.tech += targ.race.tech/100; } if (targ.race.pop <= 0) { // RIP! race.allies[targ.race.id] = -1; race.wars[targ.race.id] = -1; } } else return; // we lost :( } // colonise it! targ.race = race; targ.pop = pop / 5; pop -= pop/5; targ.age = 0; } public boolean battle(Star t) { long asum, dsum, abase, dbase, aroll, droll; if ((race.tech+1) / (t.race.tech+1) > 2) return true; // technology overwhelmed if ((t.race.tech+1) / (race.tech+1) > 3) { race.tech++; return false; } asum = race.pop / 1000 + race.tech * 500 + race.iq * 50; dsum = t.race.pop / 10000 + t.race.tech * 500 + t.race.iq * 50 + t.pop; // call in allies... for (int i = 0; i < MAX_RACES; i++) { if (race.allies[i] != -1) { asum += ((Race)races.elementAt(i)).pop/4000; } if (t.race.allies[i] != -1) { dsum += ((Race)races.elementAt(i)).pop/3000; } } if (longNumber(asum/2, asum) > longNumber(dsum/2, dsum)) { return true; } return false; } public void grow() { long tp = pop; if (pop > 0 && pop < 1000) { pop += pop/8; } else if (pop >= 1000) { pop += 100000/pop; } if (pop > 100000) pop = 100000; race.pop += pop - tp; } public void disInhabit() { race = null; age = 0; pop = 0; } public void inhabit(Race r) { race = r; age = 0; pop = 5000; r.pop += 5000; } public Star search() { // returns star that matches this star's race's current expansion "policy" int sx = x + 1, sy = y, dir = 0; // search x and y (start at position one right of this star) int dx, dy, d, bigxd; // distance stuff boolean anyinrange = false; for (;;) { if (dir == 0) { // downwards sy++; if (sy - y == sx - x) { // at corner dir = 1; // change direction } } else if (dir == 1) { // leftwards sx--; if (sy - y == x - sx) { // at corner dir = 2; // change direction } } else if (dir == 2) { // upwards sy--; if (y - sy == x - sx) { // at corner dir = 3; } } else if (dir == 3) { // rightwards sx++; if (y - sy == (sx - x) - 1) { // at corner + 1 to the right dir = 0; // check if we out of range yet dx = sx - x; if (dx > race.expd) { return null; } } } if (sx == galw || sx == -1 || sy == galh || sy == -1) { // out of bounds, end search return null; } if (starmap[sx][sy] != null) { Star cand = starmap[sx][sy]; dx = sx - x; dy = sy - y; bigxd = race.expd * race.expd; int bigd = dx*dx + dy*dy; if (bigd < bigxd) { int minTech = 0; if (cand.type == 1) { minTech = 100; } else if (cand.type == 2) { minTech = 1000; } else if (cand.type == 3) { minTech = 10000; } else if (cand.type == 4) { minTech = 100000; } else if (cand.type == 5) { minTech = 1000000; } if (race.tech >= minTech) { // ok we have the technology... if ((cand.race != null && cand.race.id != race.id && race.allies[cand.race.id] == -1) || cand.race == null) { return cand; } } } } } } } public class Race { int type, iq, age, id, expd = 15, civilWarPeriod = 0; boolean civilWar = false, RIP = false; // Vector war, ally; // war is of Wars, ally of Races int []wars = new int[MAX_RACES]; // wars int []allies = new int[MAX_RACES]; // alliances long tech, pop; Color colorYoung, colorOld; public Race(int t, int i, int nid) { id = nid; type = t; iq = i; for (int n = 0; n < MAX_RACES; n++) { wars[n] = -1; allies[n] = -1; } tech = 0; pop = 0; if (id == 0) { colorYoung = new Color(255, 0, 0); colorOld = new Color(192, 0, 0); } else if (id == 1) { colorYoung = new Color(0, 255, 0); colorOld = new Color(0, 192, 0); } else if (id == 2) { colorYoung = new Color(0, 0, 255); colorOld = new Color(0, 0, 192); } else if (id == 3) { colorYoung = new Color(255, 0, 255); colorOld = new Color(192, 0, 192); } else if (id == 4) { colorYoung = new Color(0, 255, 255); colorOld = new Color(0, 192, 192); } else if (id == 5) { colorYoung = new Color(255, 255, 0); colorOld = new Color(192, 192, 0); } } public void benefitAlliances() { long ti; if (civilWar) return; for (int n = 0; n < MAX_RACES; n++) { if (allies[n] != -1) { Race r = (Race)races.elementAt(n); ti = r.tech / 100; // gain 1% of existing allies' tech per turn tech += ti; } } } public void civilWarRoll() { if (pop > MIN_CIVIL_WAR_POP) { if (number(0, iq) < 10) { // doh!! :) civilWar = true; civilWarPeriod = 10; } } } public void paranoiaRoll() { for (int n = 0; n < MAX_RACES; n++) { if (allies[n] != -1) { Race r = (Race)races.elementAt(n); long roll1 = longNumber(0, r.pop); long roll2 = longNumber(0, pop*PARANOIA_FACTOR); if (roll1 > roll2) { // uh oh... we freak out and declare war on em :) allies[n] = -1; wars[n] = 50; r.allies[id] = -1; r.wars[id] = 50; } } } } public void checkAlliances() { if (civilWar) return; for (int n = 0; n < races.size(); n++) { Race r = (Race)races.elementAt(n); if (r != this) { boolean doAlly = true; // check our allies for (int i = 0; i < MAX_RACES; i++) { // check we not allied with people they're at war with if (allies[i] != -1) { for (int j = 0; j < MAX_RACES; j++) { if (r.wars[j] != -1 && j == i) { doAlly = false; } } } } // check we not at war with em if (wars[n] != -1) { // we at war with em doAlly = false; } // check our wars for (int i = 0; i < MAX_RACES; i++) { // check we not at war with any people they're allied with for (int j = 0; j < MAX_RACES; j++) { if (wars[i] != -1) { if (r.allies[j] != -1 && j == i) { doAlly = false; } } } } if (doAlly = false) { allies[n] = -1; r.allies[id] = -1; } else { // if peaceful, skip preconditions etc. and make it anyway if (type == 1 && r.type == 1) { allies[n] = 1; // true r.allies[id] = 1; // true } else { // otherwise, we need some common ground... i.e. wars with other races boolean makeAlliance = false; for (int i = 0; i < MAX_RACES; i++) { if (wars[i] != -1) { for (int j = 0; j < MAX_RACES; j++) { if (r.wars[j] != -1 && i == j) { makeAlliance = true; } } } } if (makeAlliance == true) { allies[n] = 1; r.allies[id] = 1; } } } } } } public void techAdvance() { long ti = (pop/MILLION) * (iq/10); if (type == 1) ti+=ti/10; // peaceful research bonus of 10% tech += ti; if (tech > TECH_MAX) tech = TECH_MAX; if (tech > RAISE_IQ && iq < MAX_IQ) { iq++; } } public void decayWars() { for (int n = 0; n < MAX_RACES; n++) { if (wars[n] > 0) { wars[n]--; if (wars[n] == 0) { wars[n] = -1; } } } if (civilWar) { civilWarPeriod--; if (civilWarPeriod == 0) { civilWar = false; } } } public void seed() { int stoptrying=5000; boolean foundone=false; Star s=null; while (!foundone && stoptrying > 0) { int x = number(20, 780); int y = number(20, 580); if (starmap[x][y] != null) { s = starmap[x][y]; if (s.race == null && s.type == 0) { foundone=true; } } stoptrying--; } if (!foundone) return; s.inhabit(this); } public void calcExpansionPolicy() { expd = 15; for (int i = 0; i < MAX_RACES; i++) { if (wars[i] != -1) { expd += 2; // will colonise an extra 2 ly for every race is at war with if (wars[i] > 10) { expd += 3; // and a further 3 for long wars, cos things may look bad } } } if (expd > MAX_COL_DIST) { expd = MAX_COL_DIST; } } } public class War { Race target; int remaining; public War(Race t, int r) { target = t; remaining = r; } } public class PlayArea extends JPanel { public PlayArea() { setSize(800, 600); } public void paint(Graphics g) { g.setColor(Color.black); g.fillRect(0,0,800,650); long max=-1; for (int i = 0; i < races.size(); i++) { Race r = (Race)races.elementAt(i); if (r.pop > max) max = r.pop; } long sf = 1 + (max / 780); for (int i = 0; i < races.size(); i++) { Race r = (Race)races.elementAt(i); long xx = r.pop/sf; g.setColor(r.colorYoung); g.drawLine(5, 605 + i*5, 5+(int)xx, 605 + i*5); } for (int y = 0; y < galh; y++) { for (int x = 0; x < galw; x++) { if (starmap[x][y] != null) { Star s = starmap[x][y]; if (s.pop > 0) { g.setColor(s.age < 75 ? s.race.colorYoung : s.race.colorOld); g.drawLine(s.x-1, s.y, s.x+1, s.y); g.drawLine(s.x, s.y-1, s.x, s.y+1); } else { g.setColor(s.starColor); g.drawLine(s.x, s.y, s.x, s.y); } } } } } } }