Importing source code for MicropolisJ, the Java rewrite of Micropolis.

This edition of Micropolis, written for the Java desktop platform,
is fairly feature complete. I believe the only missing functionality
is that of loading the built-in scenarios, and this can be implemented
if there is any demand for it.

I will soon update the home page at http://code.google.com/p/micropolis/
with downloadable packages of this edition of the software.


git-svn-id: https://micropolis.googlecode.com/svn/trunk/micropolis-java@528 d9718cc8-9f43-0410-858b-315f434eb58c
This commit is contained in:
jason@long.name 2013-02-14 21:02:42 +00:00
parent 99e2b7dd01
commit ed6795dfca
189 changed files with 13184 additions and 0 deletions

21
src/micropolisj/Main.java Normal file
View file

@ -0,0 +1,21 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj;
import micropolisj.gui.MainWindow;
public class Main
{
public static void main(String [] args)
{
MainWindow win = new MainWindow();
win.setVisible(true);
win.doNewCity(true);
}
}

View file

@ -0,0 +1,87 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
public class AirplaneSprite extends Sprite
{
int destX;
int destY;
// Note: frames 1-8 used for regular movement
// 9-11 used for Taking off
static int [] CDx = { 0, 0, 6, 8, 6, 0, -6, -8, -6, 8, 8, 8 };
static int [] CDy = { 0, -8, -6, 0, 6, 8, 6, 0, -6, 0, 0, 0 };
public AirplaneSprite(Micropolis engine, int xpos, int ypos)
{
super(engine, SpriteKind.AIR);
this.x = xpos * 16 + 8;
this.y = ypos * 16 + 8;
this.width = 48;
this.height = 48;
this.offx = -24;
this.offy = -24;
this.destY = this.y;
if (xpos > engine.getWidth()-20) {
// not enough room to east of airport for taking off
this.destX = x - 200;
this.frame = 7;
}
else {
this.destX = x + 200;
this.frame = 11;
}
}
@Override
public void moveImpl()
{
int z = this.frame;
if (city.acycle % 5 == 0) {
if (z > 8) { //plane is still taking off
z--;
if (z < 9) { z = 3; }
this.frame = z;
}
else { // go to destination
int d = getDir(x, y, destX, destY);
z = turnTo(z, d);
this.frame = z;
}
}
if (getDis(x, y, destX, destY) < 50) { // at destination
//FIXME- original code allows destination to be off-the-map
destX = city.PRNG.nextInt(city.getWidth()) * 16 + 8;
destY = city.PRNG.nextInt(city.getHeight()) * 16 + 8;
}
if (!city.noDisasters) {
boolean explode = false;
for (Sprite s : city.allSprites()) {
if (s != this &&
(s.kind == SpriteKind.AIR || s.kind == SpriteKind.COP) &&
checkSpriteCollision(s))
{
s.explodeSprite();
explode = true;
}
}
if (explode) {
explodeSprite();
}
}
this.x += CDx[z];
this.y += CDy[z];
}
}

View file

@ -0,0 +1,145 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
import static micropolisj.engine.TileConstants.*;
public class Animate
{
static final char [] aniTile = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
/* Fire */
57, 58, 59, 60, 61, 62, 63, 56,
/* No Traffic */
64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
/* Light Traffic */
128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
/* Heavy Traffic */
192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
/* Wires & Rails */
208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
/* Residential */
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271,
272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287,
288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303,
304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319,
320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335,
336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351,
352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367,
368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383,
384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399,
400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415,
416, 417, 418, 419, 420, 421, 422,
/* Commercial */
423, 424, 425, 426, 427, 428, 429, 430, 431,
432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447,
448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463,
464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479,
480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495,
496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511,
512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527,
528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543,
544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559,
560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575,
576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591,
592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607,
608, 609, 610, 611,
/* Industrial */
612, 613, 614, 615, 616, 617, 618, 619, 852, 621, 622, 623,
624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639,
640, 884, 642, 643, 888, 645, 646, 647, 648, 892, 896, 651, 652, 653, 654, 655,
656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671,
672, 673, 674, 675, 900, 904, 678, 679, 680, 681, 682, 683, 684, 685, 908, 687,
688, 912, 690, 691, 692,
/* SeaPort */
693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703,
704, 705, 706, 707, 708,
/* AirPort */
709, 710, 832, 712, 713, 714, 715, 716, 717, 718, 719,
720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735,
736, 737, 738, 739, 740, 741, 742, 743, 744,
/* Coal power */
745, 746, 916, 920, 749, 750, 924,
928, 753, 754, 755, 756, 757, 758, 759, 760,
/* Fire Dept */
761, 762, 763, 764, 765, 766, 767,
768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778,
/* Stadium */
779, 780, 781, 782, 783,
784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794,
/* Stadium Anims */
795, 796, 797, 798, 799,
800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810,
/* Nuclear Power */
811, 812, 813, 814, 815,
816, 817, 818, 819, 952, 821, 822, 823, 824, 825, 826,
/* Power out + Bridges */
827, 828, 829, 830, 831,
/* Radar dish */
833, 834, 835, 836, 837, 838, 839, 832,
/* Fountain / Flag */
841, 842, 843, 840, 845, 846, 847, 848,
849, 850, 851, 844, 853, 854, 855, 856, 857, 858, 859, 852,
/* zone destruct & rubblize */
861, 862, 863, 864,
865, 866, 867, 867,
/* totally unsure */
868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879,
880, 881, 882, 883,
/* Smoke stacks */
885, 886, 887, 884, 889, 890, 891, 888, 893, 894, 895, 892,
897, 898, 899, 896, 901, 902, 903, 900, 905, 906, 907, 904, 909, 910, 911, 908,
913, 914, 915, 912, 917, 918, 919, 916, 921, 922, 923, 920, 925, 926, 927, 924,
929, 930, 931, 928,
/* Stadium Playfield */
933, 934, 935, 936, 937, 938, 939, 932, 941, 942, 943, 944,
945, 946, 947, 940,
/* Bridge up chars */
948, 949, 950, 951,
/* Nuclear swirl */
953, 954, 955, 952,
/* */
};
static class Smoke
{
// There are eight full Industry-zone images in the tiles bank.
// This array indicates which of those eight zones have an animation.
static int [] AniThis = { 1, 0, 1, 1, 0, 0, 1, 1 };
// Up to two tiles can be animated. Arrays DX1,DXY indicate the relative
// position of the first animated tile.
static int [] DX1 = { -1, 0, 1, 0, 0, 0, 0, 1 };
static int [] DY1 = { -1, 0, -1, -1, 0, 0, -1, -1 };
// Arrays DX2,DY2 indicate the second animated tile.
static int [] DX2 = { -1, 0, 1, 1, 0, 0, 1, 1 };
static int [] DY2 = { -1, 0, 0, -1, 0, 0, -1, 0 };
static int [] AniTabA = { 0, 0, 32, 40, 0, 0, 48, 56 };
static int [] AniTabB = { 0, 0, 36, 44, 0, 0, 52, 60 };
static int [] AniTabC = { IND1, 0, IND2, IND4, 0, 0, IND6, IND8 };
static int [] AniTabD = { IND1, 0, IND3, IND5, 0, 0, IND7, IND9 };
static final int ASCBIT = (ANIMBIT | CONDBIT | BURNBIT);
static final int REGBIT = (CONDBIT | BURNBIT);
}
}

View file

@ -0,0 +1,30 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
public class BudgetNumbers
{
public int taxRate;
public int taxIncome;
public int operatingExpenses;
public int previousBalance;
public int newBalance;
public int roadRequest;
public int roadFunded;
public double roadPercent;
public int fireRequest;
public int fireFunded;
public double firePercent;
public int policeRequest;
public int policeFunded;
public double policePercent;
}

View file

@ -0,0 +1,276 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
import java.util.*;
public class CityEval
{
private final Micropolis engine;
private final Random PRNG;
public CityEval(Micropolis engine)
{
this.engine = engine;
this.PRNG = engine.PRNG;
assert PRNG != null;
}
/** Percentage of population "approving" the mayor. Derived from cityScore. */
public int cityYes;
/** Percentage of population "disapproving" the mayor. Derived from cityScore. */
public int cityNo;
/** City assessment value. */
public int cityAssValue;
/** Player's score, 0-1000. */
public int cityScore;
/** Change in cityScore since last evaluation. */
public int deltaCityScore;
/** City population as of current evaluation. */
public int cityPop;
/** Change in cityPopulation since last evaluation. */
public int deltaCityPop;
/** Classification of city size. 0==village, 1==town, etc. */
public int cityClass; // 0..5
/** City's top 4 (or fewer) problems as reported by citizens. */
public CityProblem [] problemOrder = new CityProblem[0];
/** Number of votes given for the various problems identified by problemOrder[]. */
public EnumMap<CityProblem,Integer> problemVotes = new EnumMap<>(CityProblem.class);
/** Score for various problems. */
public EnumMap<CityProblem,Integer> problemTable = new EnumMap<>(CityProblem.class);
void cityEvaluation()
{
if (engine.totalPop != 0) {
calculateAssValue();
doPopNum();
doProblems();
calculateScore();
doVotes();
} else {
evalInit();
}
engine.fireEvaluationChanged();
}
/** Evaluate an empty city. */
void evalInit()
{
cityYes = 0;
cityNo = 0;
cityAssValue = 0;
cityClass = 0;
cityScore = 500;
deltaCityScore = 0;
problemVotes.clear();
problemOrder = new CityProblem[0];
}
void calculateAssValue()
{
int z = 0;
z += engine.roadTotal * 5;
z += engine.railTotal * 10;
z += engine.policeCount * 1000;
z += engine.fireStationCount * 1000;
z += engine.hospitalCount * 400;
z += engine.stadiumCount * 3000;
z += engine.seaportCount * 5000;
z += engine.airportCount * 10000;
z += engine.coalCount * 3000;
z += engine.nuclearCount * 6000;
cityAssValue = z * 1000;
}
void doPopNum()
{
int oldCityPop = cityPop;
cityPop = engine.getCityPopulation();
deltaCityPop = cityPop - oldCityPop;
cityClass =
cityPop > 500000 ? 5 : //megalopolis
cityPop > 100000 ? 4 : //metropolis
cityPop > 50000 ? 3 : //capital
cityPop > 10000 ? 2 : //city
cityPop > 2000 ? 1 : //town
0; //village
}
void doProblems()
{
problemTable.clear();
problemTable.put(CityProblem.CRIME, engine.crimeAverage);
problemTable.put(CityProblem.POLLUTION, engine.pollutionAverage);
problemTable.put(CityProblem.HOUSING, (int)Math.round(engine.landValueAverage * 0.7));
problemTable.put(CityProblem.TAXES, engine.cityTax * 10);
problemTable.put(CityProblem.TRAFFIC, averageTrf());
problemTable.put(CityProblem.UNEMPLOYMENT, getUnemployment());
problemTable.put(CityProblem.FIRE, getFire());
problemVotes = voteProblems(problemTable);
CityProblem [] probOrder = CityProblem.values();
Arrays.sort(probOrder, new Comparator<CityProblem>() {
public int compare(CityProblem a, CityProblem b) {
return -(problemVotes.get(a).compareTo(problemVotes.get(b)));
}});
int c = 0;
while (c < probOrder.length &&
problemVotes.get(probOrder[c]).intValue() != 0 &&
c < 4)
c++;
problemOrder = new CityProblem[c];
for (int i = 0; i < c; i++) {
problemOrder[i] = probOrder[i];
}
}
EnumMap<CityProblem,Integer> voteProblems(Map<CityProblem,Integer> probTab)
{
CityProblem [] pp = CityProblem.values();
int [] votes = new int[pp.length];
int countVotes = 0;
for (int i = 0; i < 600; i++) {
if (PRNG.nextInt(301) < probTab.get(pp[i%pp.length])) {
votes[i%pp.length]++;
countVotes++;
if (countVotes >= 100)
break;
}
}
EnumMap<CityProblem,Integer> rv = new EnumMap<>(CityProblem.class);
for (int i = 0; i < pp.length; i++) {
rv.put(pp[i], votes[i]);
}
return rv;
}
int averageTrf()
{
int count = 1;
int total = 0;
for (int hy = 0; hy < engine.trfDensity.length; hy++) {
for (int hx = 0; hx < engine.trfDensity[hy].length; hx++) {
if (engine.landValueMem[hy][hx] != 0) {
total += engine.trfDensity[hy][hx];
count++;
}
}
}
engine.trafficAverage = (int)Math.round(((double)total / (double)count) * 2.4);
return engine.trafficAverage;
}
int getUnemployment()
{
int b = (engine.comPop + engine.indPop) * 8;
if (b == 0)
return 0;
double r = (double)engine.resPop / (double)b;
b = (int)Math.floor((r-1.0)*255);
if (b > 255) {
b = 255;
}
return b;
}
int getFire()
{
int z = engine.firePop * 5;
return Math.min(255, z);
}
static double clamp(double x, double min, double max)
{
return Math.max(min, Math.min(max, x));
}
void calculateScore()
{
int oldCityScore = cityScore;
int x = 0;
for (Integer z : problemTable.values()) {
x += z.intValue();
}
x /= 3;
x = Math.min(256, x);
double z = clamp((256 - x) * 4, 0, 1000);
if (engine.resCap) { z = 0.85 * z; }
if (engine.comCap) { z = 0.85 * z; }
if (engine.indCap) { z = 0.85 * z; }
if (engine.roadEffect < 32) { z -= (32 - engine.roadEffect); }
if (engine.policeEffect < 1000) { z *= (0.9 + (engine.policeEffect / 10000.1)); }
if (engine.fireEffect < 1000) { z *= (0.9 + (engine.fireEffect / 10000.1)); }
if (engine.resValve < -1000) { z *= 0.85; }
if (engine.comValve < -1000) { z *= 0.85; }
if (engine.indValve < -1000) { z *= 0.85; }
double SM = 1.0;
if (cityPop == 0 && deltaCityPop == 0) {
SM = 1.0;
}
else if (deltaCityPop == cityPop) {
SM = 1.0;
}
else if (deltaCityPop > 0) {
SM = (double)deltaCityPop / (double)cityPop + 1.0;
}
else if (deltaCityPop < 0) {
SM = 0.95 + ((double)deltaCityPop / (double)(cityPop-deltaCityPop));
}
z *= SM;
z -= getFire();
z -= engine.cityTax;
int TM = engine.unpoweredZoneCount + engine.poweredZoneCount;
SM = TM != 0 ? ((double)engine.poweredZoneCount / (double)TM) : 1.0;
z *= SM;
z = clamp(z, 0, 1000);
cityScore = (int)Math.round((cityScore + z) / 2.0);
deltaCityScore = cityScore - oldCityScore;
}
void doVotes()
{
cityYes = cityNo = 0;
for (int i = 0; i < 100; i++) {
if (PRNG.nextInt(1001) < cityScore) {
cityYes++;
} else {
cityNo++;
}
}
}
}

View file

@ -0,0 +1,45 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
public class CityLocation
{
public int x;
public int y;
public CityLocation(int x, int y)
{
this.x = x;
this.y = y;
}
@Override
public int hashCode()
{
return x*33+y;
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof CityLocation) {
CityLocation rhs = (CityLocation)obj;
return this.x == rhs.x && this.y == rhs.y;
}
else {
return false;
}
}
@Override
public String toString()
{
return "("+x+","+y+")";
}
}

View file

@ -0,0 +1,23 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
/**
* Enumeration of various city problems that the citizens complain about.
*/
public enum CityProblem
{
CRIME,
POLLUTION,
HOUSING,
TAXES,
TRAFFIC,
UNEMPLOYMENT,
FIRE;
}

View file

@ -0,0 +1,22 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
/**
* Lists the disasters that the user can invoke.
*/
public enum Disaster
{
MONSTER,
FIRE,
FLOOD,
MELTDOWN,
TORNADO,
EARTHQUAKE;
}

View file

@ -0,0 +1,17 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
/**
* The listener interface for receiving earthquake notifications.
*/
public interface EarthquakeListener
{
void earthquakeStarted();
}

View file

@ -0,0 +1,63 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
import static micropolisj.engine.TileConstants.*;
public class ExplosionSprite extends Sprite
{
public ExplosionSprite(Micropolis engine, int x, int y)
{
super(engine, SpriteKind.EXP);
this.x = x;
this.y = y;
this.width = 48;
this.height = 48;
this.offx = -24;
this.offy = -24;
this.frame = 1;
}
@Override
public void moveImpl()
{
if (city.acycle % 2 == 0) {
if (this.frame == 1) {
city.makeSound(x/16, y/16, Sound.EXPLOSION_HIGH);
city.sendMessageAt(MicropolisMessage.EXPLOSION_REPORT, x/16, y/16);
}
this.frame++;
}
if (this.frame > 6) {
this.frame = 0;
startFire(x/16, y/16);
startFire(x/16-1, y/16-1);
startFire(x/16+1, y/16-1);
startFire(x/16-1, y/16+1);
startFire(x/16+1, y/16+1);
return;
}
}
void startFire(int xpos, int ypos)
{
if (!city.testBounds(xpos, ypos))
return;
int z = city.getTile(xpos, ypos);
int t = z & LOMASK;
if ((z & BURNBIT) == 0 && t != DIRT)
return;
if ((z & ZONEBIT) != 0)
return;
city.setTile(xpos, ypos, (char)(FIRE + city.PRNG.nextInt(4) + ANIMBIT));
}
}

View file

@ -0,0 +1,34 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
public class GameLevel
{
public static final int MIN_LEVEL = 0;
public static final int MAX_LEVEL = 2;
public static boolean isValid(int lev)
{
return lev >= MIN_LEVEL && lev <= MAX_LEVEL;
}
public static int getStartingFunds(int lev)
{
switch (lev) {
case 0: return 20000;
case 1: return 10000;
case 2: return 5000;
default:
throw new Error("unexpected game level: "+lev);
}
}
//prevent this class from being instantiated
private GameLevel() {}
}

View file

@ -0,0 +1,101 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
public class HelicopterSprite extends Sprite
{
int count;
int destX;
int destY;
int origX;
int origY;
static int [] CDx = { 0, 0, 3, 5, 3, 0, -3, -5, -3 };
static int [] CDy = { 0, -5, -3, 0, 3, 5, 3, 0, -3 };
static final int SOUND_FREQ = 200;
public HelicopterSprite(Micropolis engine, int xpos, int ypos)
{
super(engine, SpriteKind.COP);
this.x = xpos * 16 + 8;
this.y = ypos * 16 + 8;
this.width = 32;
this.height = 32;
this.offx = -16;
this.offy = -16;
this.destX = city.PRNG.nextInt(city.getWidth()) * 16 + 8;
this.destY = city.PRNG.nextInt(city.getHeight()) * 16 + 8;
this.origX = x;
this.origY = y;
this.count = 1500;
this.frame = 5;
}
@Override
public void moveImpl()
{
if (this.count > 0) {
this.count--;
}
if (this.count == 0) {
// attract copter to monster and tornado so it blows up more often
if (city.hasSprite(SpriteKind.GOD)) {
MonsterSprite monster = (MonsterSprite) city.getSprite(SpriteKind.GOD);
this.destX = monster.x;
this.destY = monster.y;
}
else if (city.hasSprite(SpriteKind.TOR)) {
TornadoSprite tornado = (TornadoSprite) city.getSprite(SpriteKind.TOR);
this.destX = tornado.x;
this.destY = tornado.y;
}
else {
this.destX = origX;
this.destY = origY;
}
if (getDis(x, y, origX, origY) < 30) {
// made it back to airport, go ahead and land.
this.frame = 0;
return;
}
}
if (city.acycle % SOUND_FREQ == 0) {
// send report, if hovering over high traffic area
int xpos = this.x / 16;
int ypos = this.y / 16;
if (city.getTrafficDensity(xpos, ypos) > 170 &&
city.PRNG.nextInt(8) == 0)
{
city.sendMessageAt(MicropolisMessage.HEAVY_TRAFFIC_REPORT,
xpos, ypos);
city.makeSound(xpos, ypos, Sound.HEAVYTRAFFIC);
}
}
int z = this.frame;
if (city.acycle % 3 == 0) {
int d = getDir(x, y, destX, destY);
z = turnTo(z, d);
this.frame = z;
}
x += CDx[z];
y += CDy[z];
}
}

View file

@ -0,0 +1,541 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
import java.util.*;
import static micropolisj.engine.TileConstants.*;
public class MapGenerator
{
Micropolis engine;
char [][] map;
Random PRNG;
/**
* Three settings on whether to generate a new map as an island.
*/
static enum CreateIsland
{
NEVER,
ALWAYS,
SELDOM; // seldom == 10% of the time
}
CreateIsland createIsland = CreateIsland.SELDOM;
public MapGenerator(Micropolis engine)
{
assert engine != null;
this.engine = engine;
this.map = engine.map;
}
private int getWidth()
{
return map[0].length;
}
private int getHeight()
{
return map.length;
}
public void generateNewCity()
{
long r = Micropolis.DEFAULT_PRNG.nextLong();
generateSomeCity(r);
}
public void generateSomeCity(long r)
{
engine.totalFunds = GameLevel.getStartingFunds(engine.gameLevel);
generateMap(r);
engine.fireWholeMapChanged();
}
/**
* Level for tree creation.
* If positive, this is (roughly) the number of trees to randomly place.
* If negative, then the number of trees is randomly chosen.
* If zero, then no trees are generated.
*/
int treeLevel = -1; //level for tree creation
int curveLevel = -1; //level for river curviness; -1==auto, 0==none, >0==level
int lakeLevel = -1; //level for lake creation; -1==auto, 0==none, >0==level
void generateMap(long r)
{
PRNG = new Random(r);
if (createIsland == CreateIsland.SELDOM)
{
if (PRNG.nextInt(100) < 10) //chance that island is generated
{
makeIsland();
return;
}
}
if (createIsland == CreateIsland.ALWAYS)
{
makeNakedIsland();
}
else
{
clearMap();
}
getRandStart();
if (curveLevel != 0)
{
doRivers();
}
if (lakeLevel != 0)
{
makeLakes();
}
smoothRiver();
if (treeLevel != 0)
{
doTrees();
}
}
private void makeIsland()
{
makeNakedIsland();
smoothRiver();
doTrees();
}
private int erand(int limit)
{
return Math.min(
PRNG.nextInt(limit),
PRNG.nextInt(limit)
);
}
private void makeNakedIsland()
{
final int ISLAND_RADIUS = 18;
final int WORLD_X = getWidth();
final int WORLD_Y = getHeight();
for (int y = 0; y < WORLD_Y; y++)
{
for (int x = 0; x < WORLD_X; x++)
{
map[y][x] = RIVER;
}
}
for (int y = 5; y < WORLD_Y - 5; y++)
{
for (int x = 5; x < WORLD_X - 5; x++)
{
map[y][x] = DIRT;
}
}
for (int x = 0; x < WORLD_X - 5; x += 2)
{
mapX = x;
mapY = erand(ISLAND_RADIUS+1);
BRivPlop();
mapY = (WORLD_Y - 10) - erand(ISLAND_RADIUS+1);
BRivPlop();
mapY = 0;
SRivPlop();
mapY = WORLD_Y - 6;
SRivPlop();
}
for (int y = 0; y < WORLD_Y - 5; y += 2)
{
mapY = y;
mapX = erand(ISLAND_RADIUS+1);
BRivPlop();
mapX = (WORLD_X - 10) - erand(ISLAND_RADIUS+1);
BRivPlop();
mapX = 0;
SRivPlop();
mapX = (WORLD_X - 6);
SRivPlop();
}
}
private void clearMap()
{
for (int y = 0; y < map.length; y++)
{
for (int x = 0; x < map[y].length; x++)
{
map[y][x] = DIRT;
}
}
}
int xStart;
int yStart;
int mapX;
int mapY;
int dir;
int lastDir;
private void getRandStart()
{
xStart = 40 + PRNG.nextInt(getWidth() - 79);
yStart = 33 + PRNG.nextInt(getHeight() - 66);
mapX = xStart;
mapY = yStart;
}
private void makeLakes()
{
int lim1;
if (lakeLevel < 0)
lim1 = PRNG.nextInt(11);
else
lim1 = lakeLevel / 2;
for (int t = 0; t < lim1; t++)
{
int x = PRNG.nextInt(getWidth() - 20) + 10;
int y = PRNG.nextInt(getHeight() - 19) + 10;
int lim2 = PRNG.nextInt(13) + 2;
for (int z = 0; z < lim2; z++)
{
mapX = x - 6 + PRNG.nextInt(13);
mapY = y - 6 + PRNG.nextInt(13);
if (PRNG.nextInt(5) != 0)
SRivPlop();
else
BRivPlop();
}
}
}
private void doRivers()
{
dir = lastDir = PRNG.nextInt(4);
doBRiv();
mapX = xStart;
mapY = yStart;
dir = lastDir = lastDir ^ 4;
doBRiv();
mapX = xStart;
mapY = yStart;
lastDir = PRNG.nextInt(4);
doSRiv();
}
private void doBRiv()
{
int r1, r2;
if (curveLevel < 0)
{
r1 = 100;
r2 = 200;
}
else
{
r1 = curveLevel + 10;
r2 = curveLevel + 100;
}
while (engine.testBounds(mapX + 4, mapY + 4))
{
BRivPlop();
if (PRNG.nextInt(r1+1) < 10)
{
dir = lastDir;
}
else
{
if (PRNG.nextInt(r2+1) > 90)
{
dir++;
}
if (PRNG.nextInt(r2+1) > 90)
{
dir--;
}
}
moveMap(dir);
}
}
private void doSRiv()
{
int r1, r2;
if (curveLevel < 0)
{
r1 = 100;
r2 = 200;
}
else
{
r1 = curveLevel + 10;
r2 = curveLevel + 100;
}
while (engine.testBounds(mapX + 3, mapY + 3))
{
SRivPlop();
if (PRNG.nextInt(r1+1) < 10)
{
dir = lastDir;
}
else
{
if (PRNG.nextInt(r2+1) > 90)
{
dir++;
}
if (PRNG.nextInt(r2+1) > 90)
{
dir--;
}
}
moveMap(dir);
}
}
static final char [][] BRMatrix = new char[][] {
{ 0, 0, 0, 3, 3, 3, 0, 0, 0 },
{ 0, 0, 3, 2, 2, 2, 3, 0, 0 },
{ 0, 3, 2, 2, 2, 2, 2, 3, 0 },
{ 3, 2, 2, 2, 2, 2, 2, 2, 3 },
{ 3, 2, 2, 2, 4, 2, 2, 2, 3 },
{ 3, 2, 2, 2, 2, 2, 2, 2, 3 },
{ 0, 3, 2, 2, 2, 2, 2, 3, 0 },
{ 0, 0, 3, 2, 2, 2, 3, 0, 0 },
{ 0, 0, 0, 3, 3, 3, 0, 0, 0 }
};
private void BRivPlop()
{
for (int x = 0; x < 9; x++)
{
for (int y = 0; y < 9; y++)
{
putOnMap(BRMatrix[y][x], x, y);
}
}
}
static final char [][] SRMatrix = new char[][] {
{ 0, 0, 3, 3, 0, 0 },
{ 0, 3, 2, 2, 3, 0 },
{ 3, 2, 2, 2, 2, 3 },
{ 3, 2, 2, 2, 2, 3 },
{ 0, 3, 2, 2, 3, 0 },
{ 0, 0, 3, 3, 0, 0 }
};
private void SRivPlop()
{
for (int x = 0; x < 6; x++)
{
for (int y = 0; y < 6; y++)
{
putOnMap(SRMatrix[y][x], x, y);
}
}
}
private void putOnMap(char mapChar, int xoff, int yoff)
{
if (mapChar == 0)
return;
int xloc = mapX + xoff;
int yloc = mapY + yoff;
if (!engine.testBounds(xloc, yloc))
return;
char tmp = map[yloc][xloc];
if (tmp != DIRT)
{
tmp &= LOMASK;
if (tmp == RIVER && mapChar != CHANNEL)
return;
if (tmp == CHANNEL)
return;
}
map[yloc][xloc] = mapChar;
}
static final char [] REdTab = new char[] {
13 + BULLBIT, 13 + BULLBIT, 17 + BULLBIT, 15 + BULLBIT,
5 + BULLBIT, 2, 19 + BULLBIT, 17 + BULLBIT,
9 + BULLBIT, 11 + BULLBIT, 2, 13 + BULLBIT,
7 + BULLBIT, 9 + BULLBIT, 5 + BULLBIT, 2
};
private void smoothRiver()
{
for (int mapY = 0; mapY < map.length; mapY++)
{
for (int mapX = 0; mapX < map[mapY].length; mapX++)
{
if (map[mapY][mapX] == REDGE)
{
int bitindex = 0;
for (int z = 0; z < 4; z++)
{
bitindex <<= 1;
int xtem = mapX + DX[z];
int ytem = mapY + DY[z];
if (engine.testBounds(xtem, ytem) &&
((map[ytem][xtem] & LOMASK) != DIRT) &&
(((map[ytem][xtem] & LOMASK) < WOODS_LOW) ||
((map[ytem][xtem] & LOMASK) > WOODS_HIGH)))
{
bitindex |= 1;
}
}
char temp = REdTab[bitindex & 15];
if ((temp != RIVER) && PRNG.nextInt(2) != 0)
temp++;
map[mapY][mapX] = temp;
}
}
}
}
private void doTrees()
{
int amount;
if (treeLevel < 0)
{
amount = PRNG.nextInt(101) + 50;
}
else
{
amount = treeLevel + 3;
}
for (int x = 0; x < amount; x++)
{
int xloc = PRNG.nextInt(getWidth());
int yloc = PRNG.nextInt(getHeight());
treeSplash(xloc, yloc);
}
smoothTrees();
smoothTrees();
}
private void treeSplash(int xloc, int yloc)
{
int dis;
if (treeLevel < 0)
{
dis = PRNG.nextInt(151) + 50;
}
else
{
dis = PRNG.nextInt(101 + (treeLevel*2)) + 50;
}
mapX = xloc;
mapY = yloc;
for (int z = 0; z < dis; z++)
{
int dir = PRNG.nextInt(8);
moveMap(dir);
if (!engine.testBounds(mapX, mapY))
return;
if ((map[mapY][mapX] & LOMASK) == DIRT)
{
map[mapY][mapX] = WOODS + BLBNBIT;
}
}
}
static final int [] DIRECTION_TABX = new int[] { 0, 1, 1, 1, 0, -1, -1, -1 };
static final int [] DIRECTION_TABY = new int[] { -1, -1, 0, 1, 1, 1, 0, -1 };
private void moveMap(int dir)
{
dir = dir & 7;
mapX += DIRECTION_TABX[dir];
mapY += DIRECTION_TABY[dir];
}
static final int [] DX = new int[] { -1, 0, 1, 0 };
static final int [] DY = new int[] { 0, 1, 0, -1 };
static final char [] TEdTab = new char[] {
0, 0, 0, 34,
0, 0, 36, 35,
0, 32, 0, 33,
30, 31, 29, 37
};
private void smoothTrees()
{
for (int mapY = 0; mapY < map.length; mapY++)
{
for (int mapX = 0; mapX < map[mapY].length; mapX++)
{
if (isTree(map[mapY][mapX]))
{
int bitindex = 0;
for (int z = 0; z < 4; z++)
{
bitindex <<= 1;
int xtem = mapX + DX[z];
int ytem = mapY + DY[z];
if (engine.testBounds(xtem, ytem) &&
isTree(map[ytem][xtem]))
{
bitindex |= 1;
}
}
char temp = TEdTab[bitindex & 15];
if (temp != 0)
{
if (temp != WOODS)
{
if (((mapX + mapY) & 1) != 0)
{
temp -= 8;
}
}
map[mapY][mapX] = (char)(temp + BLBNBIT);
}
else
{
map[mapY][mapX] = temp;
}
}
}
}
}
}

View file

@ -0,0 +1,28 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
/**
* The listener interface for receiving notifications whenever a tile on
* the city map changes, or when a sprite moves or changes.
*/
public interface MapListener
{
/** Called whenever data for a specific overlay has changed. */
void mapOverlayDataChanged(MapState overlayDataType);
/** Called when a sprite moves. */
void spriteMoved(Sprite sprite);
/** Called when a map tile changes, including for animations. */
void tileChanged(int xpos, int ypos);
/** Called when the entire map should be reread and rendered. */
void wholeMapChanged();
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,30 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
/**
* Lists the various map overlay options that are available.
*/
public enum MapState
{
ALL, //ALMAP
RESIDENTIAL, //REMAP
COMMERCIAL, //COMAP
INDUSTRIAL, //INMAP
TRANSPORT, //RDMAP
POPDEN_OVERLAY, //PDMAP
GROWTHRATE_OVERLAY, //RGMAP
LANDVALUE_OVERLAY, //LVMAP
CRIME_OVERLAY, //CRMAP
POLLUTE_OVERLAY, //PLMAP
TRAFFIC_OVERLAY, //TDMAP
POWER_OVERLAY, //PRMAP
FIRE_OVERLAY, //FIMAP
POLICE_OVERLAY; //POMAP
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,64 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
/**
* Enumeration of every possible message for the user generated by the game engine.
*/
public enum MicropolisMessage
{
//orig_num generated last tested/verified
NEED_RES, // 1 doMessages 1/19
NEED_COM, // 2 doMessages 1/19
NEED_IND, // 3 doMessages 1/19
NEED_ROADS, // 4 doMessages 1/19
NEED_RAILS, // 5 doMessages 1/20
NEED_POWER, // 6 doMessages 1/19
NEED_STADIUM, // 7 doMessages 1/20
NEED_SEAPORT, // 8 doMessages 1/20
NEED_AIRPORT, // 9 doMessages
HIGH_POLLUTION, // 10 doMessages 1/20
HIGH_CRIME, // 11 doMessages 1/19
HIGH_TRAFFIC, // 12 doMessages 1/20
NEED_FIRESTATION, // 13 doMessages 1/19
NEED_POLICE, // 14 doMessages 1/19
BLACKOUTS, // 15 doMessages 1/19
HIGH_TAXES, // 16 doMessages 1/19
ROADS_NEED_FUNDING, // 17 doMessages
FIRE_NEED_FUNDING, // 18 doMessages
POLICE_NEED_FUNDING, // 19 doMessages
FIRE_REPORT, // 20
MONSTER_REPORT,
TORNADO_REPORT,
EARTHQUAKE_REPORT, // 23 makeEarthquake
PLANECRASH_REPORT,
SHIPWRECK_REPORT,
TRAIN_CRASH_REPORT,
COPTER_CRASH_REPORT,
HIGH_UNEMPLOYMENT,
OUT_OF_FUNDS_REPORT,
FIREBOMBING_REPORT, //30
NEED_PARKS,
EXPLOSION_REPORT,
INSUFFICIENT_FUNDS, // 33 MainWindow.applyCurrentTool
BULLDOZE_FIRST, // 34 MainWindow.applyCurrentTool
POP_2K_REACHED, // 35 checkGrowth 1/19
POP_10K_REACHED, // 36 checkGrowth
POP_50K_REACHED, // 37 checkGrowth
POP_100K_REACHED, // 38 checkGrowth
POP_500K_REACHED, // 39 checkGrowth
BROWNOUTS_REPORT, // 40 1/20
HEAVY_TRAFFIC_REPORT, // 41 HelicopterSprite
FLOOD_REPORT,
MELTDOWN_REPORT, // 43 doMeltdown
RIOTING_REPORT,
// added by Jason
NO_NUCLEAR_PLANTS;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,181 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
import static micropolisj.engine.TileConstants.*;
public class MonsterSprite extends Sprite
{
int count;
int soundCount;
int destX;
int destY;
int origX;
int origY;
int step;
boolean flag; //true if the monster wants to return home
//GODZILLA FRAMES
// 1...3 : northeast
// 4...6 : southeast
// 7...9 : southwest
// 10..12 : northwest
// 13 : north
// 14 : east
// 15 : south
// 16 : west
// movement deltas
static int [] Gx = { 2, 2, -2, -2, 0 };
static int [] Gy = { -2, 2, 2, -2, 0 };
static int [] ND1 = { 0, 1, 2, 3 };
static int [] ND2 = { 1, 2, 3, 0 };
static int [] nn1 = { 2, 5, 8, 11 };
static int [] nn2 = { 11, 2, 5, 8 };
public MonsterSprite(Micropolis engine, int xpos, int ypos)
{
super(engine, SpriteKind.GOD);
this.x = xpos * 16 + 8;
this.y = ypos * 16 + 8;
this.width = 48;
this.height = 48;
this.offx = -24;
this.offy = -24;
this.origX = x;
this.origY = y;
this.frame = xpos > city.getWidth() / 2 ?
(ypos > city.getHeight() / 2 ? 10 : 7) :
(ypos > city.getHeight() / 2 ? 1 : 4);
this.count = 1000;
CityLocation p = city.getLocationOfMaxPollution();
this.destX = p.x * 16 + 8;
this.destY = p.y * 16 + 8;
this.flag = false;
this.step = 1;
}
@Override
public void moveImpl()
{
if (this.frame == 0) {
return;
}
if (soundCount > 0) {
soundCount--;
}
int d = (this.frame - 1) / 3; // basic direction
int z = (this.frame - 1) % 3; // step index (only valid for d<4)
if (d < 4) { //turn n s e w
assert step == -1 || step == 1;
if (z == 2) step = -1;
if (z == 0) step = 1;
z += step;
if (getDis(x, y, destX, destY) < 60) {
// reached destination
if (!flag) {
// destination was the pollution center;
// now head for home
flag = true;
destX = origX;
destY = origY;
}
else {
// destination was origX, origY;
// hide the sprite
this.frame = 0;
return;
}
}
int c = getDir(x, y, destX, destY);
c = (c - 1) / 2; //convert to one of four basic headings
assert c >= 0 && c < 4;
if ((c != d) && city.PRNG.nextInt(11) == 0) {
// randomly determine direction to turn
if (city.PRNG.nextInt(2) == 0) {
z = ND1[d];
}
else {
z = ND2[d];
}
d = 4; //transition heading
if (soundCount == 0) {
city.makeSound(x/16, y/16, Sound.MONSTER);
soundCount = 50 + city.PRNG.nextInt(101);
}
}
}
else {
assert this.frame >= 13 && this.frame <= 16;
int z2 = (this.frame - 13) % 4;
if (city.PRNG.nextInt(4) == 0) {
int newFrame;
if (city.PRNG.nextInt(2) == 0) {
newFrame = nn1[z2];
} else {
newFrame = nn2[z2];
}
d = (newFrame-1) / 3;
z = (newFrame-1) % 3;
assert d < 4;
}
else {
d = 4;
}
}
this.frame = ((d * 3) + z) + 1;
assert this.frame >= 1 && this.frame <= 16;
this.x += Gx[d];
this.y += Gy[d];
if (this.count > 0) {
this.count--;
}
int c = getChar(x, y);
if (c == -1 ||
(c == RIVER && this.count != 0 && false)
) {
this.frame = 0; //kill zilla
}
for (Sprite s : city.allSprites())
{
if (checkSpriteCollision(s) &&
(s.kind == SpriteKind.AIR ||
s.kind == SpriteKind.COP ||
s.kind == SpriteKind.SHI ||
s.kind == SpriteKind.TRA)
) {
s.explodeSprite();
}
}
destroyTile(x / 16, y / 16);
}
}

View file

@ -0,0 +1,136 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
import static micropolisj.engine.TileConstants.*;
public class ShipSprite extends Sprite
{
static int [] BDx = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
static int [] BDy = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };
static int [] BPx = { 0, 0, 2, 2, 2, 0, -2, -2, -2 };
static int [] BPy = { 0, -2, -2, 0, 2, 2, 2, 0, -2 };
static int [] BtClrTab = { RIVER, CHANNEL, POWERBASE, POWERBASE+1,
RAILBASE, RAILBASE+1, BRWH, BRWV };
int newDir;
int count;
int soundCount;
public static final int NORTH_EDGE = 5;
public static final int EAST_EDGE = 7;
public static final int SOUTH_EDGE = 1;
public static final int WEST_EDGE = 3;
public ShipSprite(Micropolis engine, int xpos, int ypos, int edge)
{
super(engine, SpriteKind.SHI);
this.x = xpos * 16 + 8;
this.y = ypos * 16 + 8;
this.width = 48;
this.height = 48;
this.offx = -24;
this.offy = -24;
this.frame = edge;
this.newDir = edge;
this.dir = 10;
this.count = 1;
}
@Override
public void moveImpl()
{
int t = RIVER;
this.soundCount--;
if (this.soundCount <= 0) {
if (city.PRNG.nextInt(4) == 0) {
city.makeSound(x/16,y/16,Sound.HONKHONK_LOW);
}
this.soundCount = 200;
}
this.count--;
if (this.count <= 0) {
this.count = 9;
if (this.newDir != this.frame) {
this.frame = turnTo(this.frame, this.newDir);
return;
}
int tem = city.PRNG.nextInt(8);
int pem;
for (pem = tem; pem < (tem + 8); pem++) {
int z = (pem % 8) + 1;
if (z == this.dir)
continue;
int xpos = this.x / 16 + BDx[z];
int ypos = this.y / 16 + BDy[z];
if (city.testBounds(xpos, ypos)) {
t = city.getTile(xpos, ypos) & LOMASK;
if ((t == CHANNEL) || (t == BRWH) || (t == BRWV) ||
tryOther(t, this.dir, z))
{
this.newDir = z;
this.frame = turnTo(this.frame, this.newDir);
this.dir = z + 4;
if (this.dir > 8) { this.dir -= 8; }
break;
}
}
}
if (pem == (tem + 8)) {
this.dir = 10;
this.newDir = city.PRNG.nextInt(8)+1;
}
}
else {
int z = this.frame;
if (z == this.newDir) {
this.x += BPx[z];
this.y += BPy[z];
}
}
if (!spriteInBounds()) {
this.frame = 0;
return;
}
boolean found = false;
for (int z : BtClrTab) {
if (t == z) {
found = true;
}
}
if (!found) {
explodeSprite();
destroyTile(x/16, y/16);
}
}
boolean tryOther(int tile, int oldDir, int newDir)
{
int z = oldDir + 4;
if (z > 8) z -= 8;
if (newDir != z) return false;
return (tile == POWERBASE || tile == POWERBASE+1 ||
tile == RAILBASE || tile == RAILBASE+1);
}
boolean spriteInBounds()
{
int xpos = x / 16;
int ypos = y / 16;
return city.testBounds(xpos, ypos);
}
}

View file

@ -0,0 +1,47 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
import java.net.URL;
/**
* Enumerates the various sounds that the city may produce.
* The engine is not responsible for actually playing the sound. That task
* belongs to the front-end (i.e. the user interface).
*/
public enum Sound
{
EXPLOSION_LOW ("explosion-low"),
EXPLOSION_HIGH("explosion-high"),
EXPLOSION_BOTH("explosion-low"),
UHUH ("bop"),
SORRY ("bop"),
BUILD ("layzone"),
BULLDOZE (null),
HONKHONK_LOW ("honkhonk-low"),
HONKHONK_MED ("honkhonk-med"),
HONKHONK_HIGH ("honkhonk-high"),
HONKHONK_HI ("honkhonk-hi"),
SIREN ("siren"),
HEAVYTRAFFIC ("heavytraffic"),
MONSTER ("zombie-roar-5");
String wavName;
private Sound(String wavName)
{
this.wavName = wavName;
}
public URL getAudioFile()
{
String n2 = "/sounds/"+wavName+".wav";
URL u = Sound.class.getResource(n2);
return u;
}
}

View file

@ -0,0 +1,37 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
/**
* Lists the simulation speeds available.
*/
public enum Speed
{
PAUSED ( 999,999, 0),
SLOW ( 500, 5, 1), //one step every 2500 ms
NORMAL ( 125, 2, 1), //one step every 250 ms
FAST ( 50, 1, 2), //one step every 25 ms
SUPER_FAST( 25, 1, 10); //one step every 2.5 ms
/** The animation speed, expressed as an interval in milliseconds. */
public final int animationDelay;
/** For slower speeds, how many animation occur for every simulation step.
* Faster speeds should set this to one. */
public final int aniFramesPerStep;
/** For faster speeds, how many simulation steps should occur for every
* update to the screen. */
public final int simStepsPerUpdate;
private Speed(int delay, int aniFrames, int simSteps)
{
this.animationDelay = delay;
this.aniFramesPerStep = aniFrames;
this.simStepsPerUpdate = simSteps;
}
}

View file

@ -0,0 +1,201 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
import static micropolisj.engine.TileConstants.*;
public abstract class Sprite
{
Micropolis city;
//TODO- enforce read-only nature of the following properties
// (i.e. do not let them be modified directly by other classes)
public SpriteKind kind;
public int offx;
public int offy;
public int width = 32;
public int height = 32;
public int frame;
public int x;
public int y;
public int lastX;
public int lastY;
int dir;
protected Sprite(Micropolis engine, SpriteKind kind)
{
this.city = engine;
this.kind = kind;
}
protected final int getChar(int x, int y)
{
int xpos = x / 16;
int ypos = y / 16;
if (city.testBounds(xpos, ypos)) {
return (city.getTile(xpos, ypos) & LOMASK);
} else {
return -1;
}
}
/**
* For subclasses to override. Actually does the movement and animation
* of this particular sprite. Setting this.frame to zero will cause the
* sprite to be unallocated.
*/
protected abstract void moveImpl();
public final void move()
{
lastX = x;
lastY = y;
moveImpl();
city.fireSpriteMoved(this);
}
public final boolean isVisible()
{
return this.frame != 0;
}
/**
* Computes direction from one point to another.
* @return integer between 1 and 8, with
* 1 == north,
* 3 == east,
* 5 == south,
* 7 == west.
*/
static final int getDir(int orgX, int orgY, int desX, int desY)
{
final int Gdtab [] = { 0, 3, 2, 1, 3, 4, 5, 7, 6, 5, 7, 8, 1 };
int dispX = desX - orgX;
int dispY = desY - orgY;
int z = dispX < 0 ? (dispY < 0 ? 11 : 8) : (dispY < 0 ? 2 : 5);
dispX = Math.abs(dispX);
dispY = Math.abs(dispY);
int absDist = dispX + dispY;
if (dispX * 2 < dispY) z++;
else if (dispY * 2 < dispX) z--;
if (z >= 1 && z <= 12) {
return Gdtab[z];
}
else {
assert false;
return 0;
}
}
/**
* Computes manhatten distance between two points.
*/
static final int getDis(int x0, int y0, int x1, int y1)
{
return Math.abs(x0-x1) + Math.abs(y0-y1);
}
final void explodeSprite()
{
this.frame = 0;
city.makeExplosionAt(x, y);
int xpos = x/16;
int ypos = y/16;
switch (kind) {
case AIR:
city.crashLocation = new CityLocation(xpos, ypos);
city.sendMessageAtPic(MicropolisMessage.PLANECRASH_REPORT, xpos, ypos);
break;
case SHI:
city.crashLocation = new CityLocation(xpos, ypos);
city.sendMessageAtPic(MicropolisMessage.SHIPWRECK_REPORT, xpos, ypos);
break;
case TRA:
case BUS:
city.crashLocation = new CityLocation(xpos, ypos);
city.sendMessageAtPic(MicropolisMessage.TRAIN_CRASH_REPORT, xpos, ypos);
break;
case COP:
city.crashLocation = new CityLocation(xpos, ypos);
city.sendMessageAtPic(MicropolisMessage.COPTER_CRASH_REPORT, xpos, ypos);
break;
}
city.makeSound(xpos, ypos, Sound.EXPLOSION_HIGH);
}
final boolean checkSpriteCollision(Sprite otherSprite)
{
if (!isVisible()) return false;
if (!otherSprite.isVisible()) return false;
return (getDis(this.x, this.y, otherSprite.x, otherSprite.y) < 30);
}
final void destroyTile(int xpos, int ypos)
{
if (!city.testBounds(xpos, ypos))
return;
int z = city.getTile(xpos, ypos);
int t = z & LOMASK;
if (t >= TREEBASE) {
if (TileConstants.isBridge(z)) {
city.setTile(xpos, ypos, RIVER);
return;
}
if ((z & BURNBIT) == 0) {
return; //cannot destroy it
}
if ((z & ZONEBIT) != 0) {
city.fireZone(xpos, ypos, z);
if (t > RZB) {
city.makeExplosion(xpos, ypos);
}
}
if (TileConstants.checkWet(t)) {
city.setTile(xpos, ypos, RIVER);
}
else {
city.setTile(xpos, ypos,
(char) (TINYEXP | BULLBIT | ANIMBIT));
}
}
}
static final int turnTo(int p, int d)
{
if (p == d)
return p;
if (p < d) {
if (d - p < 4) p++;
else p--;
}
else {
if (p - d < 4) p--;
else p++;
}
if (p > 8) return 1;
if (p < 1) return 8;
return p;
}
}

View file

@ -0,0 +1,33 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
/**
* Enumeration of the various kinds of sprites that may appear in the city.
*/
public enum SpriteKind
{
TRA(1,5),
COP(2,8),
AIR(3,11),
SHI(4,8),
GOD(5,16),
TOR(6,3),
EXP(7,6),
BUS(8,4);
public final int objectId;
public final int numFrames;
private SpriteKind(int objectId, int numFrames)
{
this.objectId = objectId;
this.numFrames = numFrames;
}
}

View file

@ -0,0 +1,327 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
import java.util.Arrays;
public class TileConstants
{
//
// terrain mapping
//
public static final char DIRT = 0;
public static final char RIVER = 2;
public static final char REDGE = 3;
public static final char CHANNEL = 4;
public static final char FIRSTRIVEDGE = 5;
public static final char LASTRIVEDGE = 20;
public static final char TREEBASE = 21;
public static final char WOODS_LOW = TREEBASE;
public static final char WOODS = 37;
public static final char UNUSED_TRASH2 = 39;
public static final char WOODS_HIGH = UNUSED_TRASH2;
public static final char WOODS2 = 40;
public static final char WOODS5 = 43;
public static final char RUBBLE = 44;
public static final char LASTRUBBLE = 47;
public static final char FLOOD = 48;
public static final char LASTFLOOD = 51;
public static final char RADTILE = 52;
public static final char FIRE = 56;
public static final char FIREBASE = 56;
public static final char ROADBASE = 64;
public static final char HBRIDGE = 64;
public static final char VBRIDGE = 65;
public static final char ROADS = 66;
public static final char ROADS2 = 67;
public static final char ROADS3 = 68;
public static final char ROADS4 = 69;
public static final char ROADS5 = 70;
public static final char ROADS6 = 71;
public static final char ROADS7 = 72;
public static final char ROADS8 = 73;
public static final char ROADS9 = 74;
public static final char ROADS10 = 75;
public static final char INTERSECTION = 76;
public static final char HROADPOWER = 77;
public static final char VROADPOWER = 78;
public static final char BRWH = 79;
public static final char LTRFBASE = 80;
public static final char BRWV = 95;
public static final char HTRFBASE = 144;
public static final char LASTROAD = 206;
public static final char POWERBASE = 208;
public static final char HPOWER = 208;
public static final char VPOWER = 209;
public static final char LHPOWER = 210;
public static final char LVPOWER = 211;
public static final char LVPOWER2 = 212;
public static final char LVPOWER3 = 213;
public static final char LVPOWER4 = 214;
public static final char LVPOWER5 = 215;
public static final char LVPOWER6 = 216;
public static final char LVPOWER7 = 217;
public static final char LVPOWER8 = 218;
public static final char LVPOWER9 = 219;
public static final char LVPOWER10 = 220;
public static final char RAILHPOWERV = 221;
public static final char RAILVPOWERH = 222;
public static final char LASTPOWER = 222;
public static final char RAILBASE = 224;
public static final char HRAIL = 224;
public static final char VRAIL = 225;
public static final char LHRAIL = 226;
public static final char LVRAIL = 227;
public static final char LVRAIL2 = 228;
public static final char LVRAIL3 = 229;
public static final char LVRAIL4 = 230;
public static final char LVRAIL5 = 231;
public static final char LVRAIL6 = 232;
public static final char LVRAIL7 = 233;
public static final char LVRAIL8 = 234;
public static final char LVRAIL9 = 235;
public static final char LVRAIL10 = 236;
public static final char HRAILROAD = 237;
public static final char VRAILROAD = 238;
public static final char LASTRAIL = 238;
public static final char RESBASE = 240;
public static final char FREEZ = 244; //free zone?
public static final char HOUSE = 249;
public static final char LHTHR = 249; //12 house tiles
public static final char HHTHR = 260;
public static final char RZB = 265; //residential zone base
public static final char HOSPITAL = 409;
public static final char CHURCH = 418;
public static final char COMBASE = 423;
public static final char COMCLR = 427;
public static final char CZB = 436; //commercial zone base
public static final char COMLAST = 609;
public static final char INDBASE = 612;
public static final char INDCLR = 616;
public static final char LASTIND = 620;
public static final char IND1 = 621;
public static final char IZB = 625;
public static final char IND2 = 641;
public static final char IND3 = 644;
public static final char IND4 = 649;
public static final char IND5 = 650;
public static final char IND6 = 676;
public static final char IND7 = 677;
public static final char IND8 = 686;
public static final char IND9 = 689;
public static final char PORTBASE = 693;
public static final char PORT = 698;
public static final char LASTPORT = 708;
public static final char AIRPORTBASE = 709;
public static final char RADAR = 711;
public static final char AIRPORT = 716;
public static final char COALBASE = 745;
public static final char POWERPLANT = 750;
public static final char LASTPOWERPLANT = 760;
public static final char FIRESTBASE = 761;
public static final char FIRESTATION = 765;
public static final char POLICESTBASE = 770;
public static final char POLICESTATION = 774;
public static final char STADIUMBASE = 779;
public static final char STADIUM = 784;
public static final char FULLSTADIUM = 800;
public static final char NUCLEARBASE = 811;
public static final char NUCLEAR = 816;
public static final char LASTZONE = 826;
public static final char LIGHTNINGBOLT = 827;
public static final char HBRDG0 = 828;
public static final char HBRDG1 = 829;
public static final char HBRDG2 = 830;
public static final char HBRDG3 = 831;
public static final char RADAR_ANIM = 832;
public static final char FOUNTAIN = 840;
public static final char INDBASE2 = 844;
public static final char SMOKEBASE = 852;
public static final char TINYEXP = 860;
public static final char SOMETINYEXP = 864;
public static final char LASTTINYEXP = 867;
public static final char COALSMOKE1 = 916;
public static final char COALSMOKE2 = 920;
public static final char COALSMOKE3 = 924;
public static final char COALSMOKE4 = 928;
public static final char FOOTBALLGAME1 = 932;
public static final char FOOTBALLGAME2 = 940;
public static final char VBRDG0 = 948;
public static final char VBRDG1 = 949;
public static final char VBRDG2 = 950;
public static final char VBRDG3 = 951;
public static final char URANIUM_FUEL = 952;
public static final char LAST_TILE = 956;
static final char [] RoadTable = new char[] {
ROADS, ROADS2, ROADS, ROADS3,
ROADS2, ROADS2, ROADS4, ROADS8,
ROADS, ROADS6, ROADS, ROADS7,
ROADS5, ROADS10, ROADS9, INTERSECTION
};
static final char [] RailTable = new char[] {
LHRAIL, LVRAIL, LHRAIL, LVRAIL2,
LVRAIL, LVRAIL, LVRAIL3, LVRAIL7,
LHRAIL, LVRAIL5, LHRAIL, LVRAIL6,
LVRAIL4, LVRAIL9, LVRAIL8, LVRAIL10
};
static final char [] WireTable = new char[] {
LHPOWER, LVPOWER, LHPOWER, LVPOWER2,
LVPOWER, LVPOWER, LVPOWER3, LVPOWER7,
LHPOWER, LVPOWER5, LHPOWER, LVPOWER6,
LVPOWER4, LVPOWER9, LVPOWER8, LVPOWER10
};
//
// status bits
//
public static final char PWRBIT = 32768; // bit 15 ... currently powered
public static final char CONDBIT = 16384; // bit 14 ... can conduct power
public static final char BURNBIT = 8192; // bit 13 ... is combustible
public static final char BULLBIT = 4096; // bit 12 ... is bulldozable
public static final char ANIMBIT = 2048; // bit 11 ... animates
public static final char ZONEBIT = 1024; // bit 10 ... is the special tile for a zone
public static final char ALLBITS = 64512; // mask for upper 6 bits
public static final char LOMASK = 1023; //mask for low 10 bits
public static final char BLBNBIT = (BULLBIT | BURNBIT);
public static final char BLBNCNBIT = (BULLBIT | BURNBIT | CONDBIT);
public static final char BNCNBIT = (BURNBIT | CONDBIT);
private TileConstants() {}
private static int [] buildingBases = {
DIRT, RIVER, TREEBASE, RUBBLE,
FLOOD, RADTILE, FIRE, ROADBASE,
POWERBASE, RAILBASE, RESBASE, COMBASE,
INDBASE, PORTBASE, AIRPORTBASE, COALBASE,
FIRESTBASE, POLICESTBASE, STADIUMBASE, NUCLEARBASE,
HBRDG0, RADAR_ANIM, FOUNTAIN, INDBASE2,
FOOTBALLGAME1, VBRDG0, URANIUM_FUEL, LAST_TILE
};
//used by queryZoneStatus
public static int getBuildingId(int tile)
{
tile &= LOMASK;
int i = Arrays.binarySearch(buildingBases, tile);
if (i >= 0) {
return i;
} else {
return -i - 2;
}
}
//used by setFire()
public static boolean isArsonable(int tile)
{
return (
(tile & ZONEBIT) == 0 &&
(tile & LOMASK) >= LHTHR &&
(tile & LOMASK) <= LASTZONE
);
}
//used by Sprite::destroyTile
public static boolean isBridge(int tile)
{
return (((tile & LOMASK) >= ROADBASE && (tile & LOMASK) <= LASTROAD)
&& ((tile & BURNBIT) == 0));
}
public static boolean isOverWater(char cell)
{
switch (cell & LOMASK)
{
case HBRIDGE:
case VBRIDGE:
case BRWV:
case BRWH:
case HBRDG0:
case HBRDG1:
case HBRDG2:
case HBRDG3:
case VBRDG0:
case VBRDG1:
case VBRDG2:
case VBRDG3:
case HPOWER:
case VPOWER:
case HRAIL:
case VRAIL:
return true;
default:
return false;
}
}
public static boolean isRubble(char cell)
{
return (((cell & LOMASK) >= RUBBLE) &&
((cell & LOMASK) <= LASTRUBBLE));
}
public static boolean isTree(char cell)
{
return (((cell & LOMASK) >= WOODS_LOW) &&
((cell & LOMASK) <= WOODS_HIGH));
}
//used by makeEarthquake
public static boolean isVulnerable(int tile)
{
int tem2 = tile & LOMASK;
if (tem2 < RESBASE ||
tem2 > LASTZONE ||
(tile & ZONEBIT) != 0
) {
return false;
} else {
return true;
}
}
public static boolean checkWet(int tile)
{
int x = tile & LOMASK;
return (x == POWERBASE ||
x == POWERBASE+1 ||
x == RAILBASE ||
x == RAILBASE + 1 ||
x == BRWH ||
x == BRWV);
}
public static int getZoneSizeFor(int tile)
{
int ch = tile & LOMASK;
if (ch < PORTBASE) {
return 3;
}
else if (ch == AIRPORT) {
return 6;
}
else {
return 4;
}
}
static boolean isRiverEdge(int tile)
{
return (tile & LOMASK) > 4 && (tile & LOMASK) < 21;
}
static boolean isFloodable(int tile)
{
return (tile == DIRT || ((tile & BULLBIT) != 0 && (tile & BURNBIT) != 0));
}
}

View file

@ -0,0 +1,20 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
/**
* Lists the various results that may occur when applying a tool.
*/
public enum ToolResult
{
SUCCESS, // 1
NONE, // 0
UH_OH, // -1; invalid position
INSUFFICIENT_FUNDS; // -2
}

View file

@ -0,0 +1,82 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
public class TornadoSprite extends Sprite
{
static int [] CDx = { 2, 3, 2, 0, -2, -3 };
static int [] CDy = { -2, 0, 2, 3, 2, 0 };
boolean flag;
int count;
public TornadoSprite(Micropolis engine, int xpos, int ypos)
{
super(engine, SpriteKind.TOR);
this.x = xpos * 16 + 8;
this.y = ypos * 16 + 8;
this.width = 48;
this.height = 48;
this.offx = -24;
this.offy = -40;
this.frame = 1;
this.count = 200;
}
@Override
public void moveImpl()
{
int z = this.frame;
if (z == 2) {
//cycle animation
if (this.flag)
z = 3;
else
z = 1;
}
else {
this.flag = (z == 1);
z = 2;
}
if (this.count > 0) {
this.count--;
}
this.frame = z;
for (Sprite s : city.allSprites()) {
if (checkSpriteCollision(s) &&
(s.kind == SpriteKind.AIR ||
s.kind == SpriteKind.COP ||
s.kind == SpriteKind.SHI ||
s.kind == SpriteKind.TRA)
) {
s.explodeSprite();
}
}
int zz = city.PRNG.nextInt(CDx.length);
x += CDx[zz];
y += CDy[zz];
if (!city.testBounds(x/16, y/16)) {
// out of bounds
this.frame = 0;
return;
}
// FIXME- the original code checks here for an ending condition
destroyTile(x/16, y/16);
}
}

View file

@ -0,0 +1,245 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
import java.util.*;
import static micropolisj.engine.TileConstants.*;
class TrafficGen
{
Micropolis engine;
int mapX;
int mapY;
Micropolis.ZoneType sourceZone;
int lastdir;
Stack<Position> positions = new Stack<>();
static final int MAX_TRAFFIC_DISTANCE = 30;
public TrafficGen(Micropolis engine)
{
this.engine = engine;
}
int makeTraffic()
{
if (findPerimeterRoad()) //look for road on this zone's perimeter
{
if (tryDrive()) //attempt to drive somewhere
{
// success; incr trafdensity
setTrafficMem();
return 1;
}
return 0;
}
else
{
// no road found
return -1;
}
}
void setTrafficMem()
{
while (!positions.isEmpty())
{
Position pos = positions.pop();
mapX = pos.x;
mapY = pos.y;
assert engine.testBounds(mapX, mapY);
int tile = engine.getTile(mapX, mapY) & LOMASK;
if (tile >= ROADBASE && tile < POWERBASE)
{
// check for rail
int z = engine.trfDensity[mapY/2][mapX/2];
z += 50;
if (z > 240 && engine.PRNG.nextInt(6) == 0)
{
z = 240;
engine.trafficMaxLocationX = mapX;
engine.trafficMaxLocationY = mapY;
HelicopterSprite copter = (HelicopterSprite) engine.getSprite(SpriteKind.COP);
if (copter != null) {
copter.destX = mapX;
copter.destY = mapY;
}
}
engine.trfDensity[mapY/2][mapX/2] = z;
}
}
}
static final int [] PerimX = { -1, 0, 1, 2, 2, 2, 1, 0,-1, -2,-2,-2 };
static final int [] PerimY = { -2,-2,-2, -1, 0, 1, 2, 2, 2, 1, 0,-1 };
boolean findPerimeterRoad()
{
for (int z = 0; z < 12; z++)
{
int tx = mapX + PerimX[z];
int ty = mapY + PerimY[z];
if (engine.testBounds(tx, ty)
&& roadTest(tx, ty))
{
mapX = tx;
mapY = ty;
return true;
}
}
return false;
}
boolean roadTest(int tx, int ty)
{
char c = engine.getTile(tx, ty);
c &= LOMASK;
if (c < ROADBASE)
return false;
else if (c > LASTRAIL)
return false;
else if (c >= POWERBASE && c < LASTPOWER)
return false;
else
return true;
}
boolean tryDrive()
{
lastdir = 5;
positions.clear();
for (int z = 0; z < MAX_TRAFFIC_DISTANCE; z++) //maximum distance to try
{
if (tryGo(z))
{
// got a road
if (driveDone())
{
// destination reached
return true;
}
}
else
{
// deadend, try backing up
if (!positions.isEmpty())
{
positions.pop();
z += 3;
}
else
{
return false;
}
}
}
// gone maxdis
return false;
}
static final int [] DX = { 0, 1, 0, -1 };
static final int [] DY = { -1, 0, 1, 0 };
boolean tryGo(int z)
{
// random starting direction
int rdir = engine.PRNG.nextInt(4);
for (int d = rdir; d < rdir + 4; d++)
{
int realdir = d % 4;
if (realdir == lastdir)
continue;
if (roadTest(mapX + DX[realdir], mapY + DY[realdir]))
{
mapX += DX[realdir];
mapY += DY[realdir];
lastdir = (realdir + 2) % 4;
if (z % 2 == 1)
{
// save pos every other move
positions.push(new Position(mapX, mapY));
}
return true;
}
}
return false;
}
static class Position
{
int x;
int y;
Position(int x, int y)
{
this.x = x;
this.y = y;
}
}
boolean driveDone()
{
int low, high;
switch (sourceZone)
{
case RESIDENTIAL:
low = COMBASE;
high = NUCLEAR;
break;
case COMMERCIAL:
low = LHTHR;
high = PORT;
break;
case INDUSTRIAL:
low = LHTHR;
high = COMBASE;
break;
default:
throw new Error("unreachable");
}
if (mapY > 0)
{
int tile = engine.getTile(mapX, mapY-1) & LOMASK;
if (tile >= low && tile <= high)
return true;
}
if (mapX + 1 < engine.getWidth())
{
int tile = engine.getTile(mapX + 1, mapY) & LOMASK;
if (tile >= low && tile <= high)
return true;
}
if (mapY + 1 < engine.getHeight())
{
int tile = engine.getTile(mapX, mapY + 1) & LOMASK;
if (tile >= low && tile <= high)
return true;
}
if (mapX > 0)
{
int tile = engine.getTile(mapX - 1, mapY) & LOMASK;
if (tile >= low && tile <= high)
return true;
}
return false;
}
}

View file

@ -0,0 +1,90 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
import static micropolisj.engine.TileConstants.*;
public class TrainSprite extends Sprite
{
static int [] Cx = { 0, 16, 0, -16 };
static int [] Cy = { -16, 0, 16, 0 };
static int [] Dx = { 0, 4, 0, -4, 0 };
static int [] Dy = { -4, 0, 4, 0, 0 };
static int [] TrainPic2 = { 1, 2, 1, 2, 5 };
static final int TRA_GROOVE_X = 8;
static final int TRA_GROOVE_Y = 8;
static final int FRAME_NORTHSOUTH = 1;
static final int FRAME_EASTWEST = 2;
static final int FRAME_NW_SE = 3;
static final int FRAME_SW_NE = 4;
static final int FRAME_UNDERWATER = 5;
public TrainSprite(Micropolis engine, int xpos, int ypos)
{
super(engine, SpriteKind.TRA);
this.x = xpos * 16 + TRA_GROOVE_X;
this.y = ypos * 16 + TRA_GROOVE_Y;
this.offx = -16;
this.offy = -16;
this.dir = 4; //not moving
}
@Override
public void moveImpl()
{
if (frame == 3 || frame == 4) {
frame = TrainPic2[this.dir];
}
x += Dx[this.dir];
y += Dy[this.dir];
if (city.acycle % 4 == 0) {
// should be at the center of a cell, if not, correct it
x = (x/16) * 16 + TRA_GROOVE_X;
y = (y/16) * 16 + TRA_GROOVE_Y;
int d1 = city.PRNG.nextInt(4);
for (int z = d1; z < d1 + 4; z++) {
int d2 = z % 4;
if (this.dir != 4) { //impossible?
if (d2 == (this.dir + 2) % 4)
continue;
}
int c = getChar(this.x + Cx[d2], this.y + Cy[d2]);
if (((c >= RAILBASE) && (c <= LASTRAIL)) || //track?
(c == RAILVPOWERH) ||
(c == RAILHPOWERV))
{
if ((this.dir != d2) && (this.dir != 4)) {
if (this.dir + d2 == 3)
this.frame = 3;
else
this.frame = 4;
}
else {
this.frame = TrainPic2[d2];
}
if ((c == RAILBASE) || (c == (RAILBASE+1))) {
//underwater
this.frame = 5;
}
this.dir = d2;
return;
}
}
if (this.dir == 4) {
// train has nowhere to go, so retire
this.frame = 0;
return;
}
this.dir = 4;
}
}
}

View file

@ -0,0 +1,19 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.engine;
public class ZoneStatus
{
public int building;
public int popDensity;
public int landValue;
public int crimeLevel;
public int pollution;
public int growthRate;
}

View file

@ -0,0 +1,4 @@
<html><head></head>
<body>
<p>Contains the backend classes that implement the actual city simulation.</p>
</body></html>

View file

@ -0,0 +1,335 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.gui;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.text.NumberFormat;
import java.util.*;
import micropolisj.engine.*;
import static micropolisj.gui.MainWindow.formatFunds;
import static micropolisj.gui.MainWindow.formatGameDate;
public class BudgetDialog extends JDialog
{
Micropolis engine;
JSpinner taxRateEntry;
int origTaxRate;
double origRoadPct;
double origFirePct;
double origPolicePct;
JLabel roadFundAlloc = new JLabel();
JSpinner roadFundEntry;
JLabel policeFundAlloc = new JLabel();
JSpinner policeFundEntry;
JLabel fireFundAlloc = new JLabel();
JSpinner fireFundEntry;
JLabel taxRevenueLbl = new JLabel();
static ResourceBundle strings = MainWindow.strings;
JCheckBox autoBudgetBtn = new JCheckBox(strings.getString("budgetdlg.auto_budget"));
JCheckBox pauseBtn = new JCheckBox(strings.getString("budgetdlg.pause_game"));
private void applyChange()
{
int newTaxRate = ((Number) taxRateEntry.getValue()).intValue();
int newRoadPct = ((Number) roadFundEntry.getValue()).intValue();
int newPolicePct = ((Number) policeFundEntry.getValue()).intValue();
int newFirePct = ((Number) fireFundEntry.getValue()).intValue();
engine.cityTax = newTaxRate;
engine.roadPercent = (double)newRoadPct / 100.0;
engine.policePercent = (double)newPolicePct / 100.0;
engine.firePercent = (double)newFirePct / 100.0;
loadBudgetNumbers(false);
}
private void loadBudgetNumbers(boolean updateEntries)
{
BudgetNumbers b = engine.generateBudget();
if (updateEntries)
{
taxRateEntry.setValue(b.taxRate);
roadFundEntry.setValue((int)Math.round(b.roadPercent*100.0));
policeFundEntry.setValue((int)Math.round(b.policePercent*100.0));
fireFundEntry.setValue((int)Math.round(b.firePercent*100.0));
}
taxRevenueLbl.setText(formatFunds(b.taxIncome));
roadFundAlloc.setText(formatFunds(b.roadFunded));
policeFundAlloc.setText(formatFunds(b.policeFunded));
fireFundAlloc.setText(formatFunds(b.fireFunded));
}
public BudgetDialog(Window owner, Micropolis engine)
{
super(owner);
setTitle(strings.getString("budgetdlg.title"));
this.engine = engine;
this.origTaxRate = engine.cityTax;
this.origRoadPct = engine.roadPercent;
this.origFirePct = engine.firePercent;
this.origPolicePct = engine.policePercent;
// give text fields of the fund-level spinners a minimum size
taxRateEntry = new JSpinner(new SpinnerNumberModel(7,0,20,1));
roadFundEntry = new JSpinner(new SpinnerNumberModel(100,0,100,1));
fireFundEntry = new JSpinner(new SpinnerNumberModel(1,0,100,1));
policeFundEntry = new JSpinner(new SpinnerNumberModel(10,0,100,1));
ChangeListener change = new ChangeListener() {
public void stateChanged(ChangeEvent ev) {
applyChange();
}
};
taxRateEntry.addChangeListener(change);
roadFundEntry.addChangeListener(change);
fireFundEntry.addChangeListener(change);
policeFundEntry.addChangeListener(change);
Box mainBox = new Box(BoxLayout.Y_AXIS);
mainBox.setBorder(BorderFactory.createEmptyBorder(8,8,8,8));
add(mainBox, BorderLayout.CENTER);
mainBox.add(makeTaxPane());
JSeparator sep = new JSeparator(SwingConstants.HORIZONTAL);
mainBox.add(sep);
JPanel fundingRatesPane = new JPanel(new GridBagLayout());
fundingRatesPane.setBorder(BorderFactory.createEmptyBorder(8,0,8,0));
mainBox.add(fundingRatesPane);
GridBagConstraints c0 = new GridBagConstraints();
c0.gridx = 0;
c0.weightx = 0.25;
c0.anchor = GridBagConstraints.WEST;
GridBagConstraints c1 = new GridBagConstraints();
c1.gridx = 1;
c1.weightx = 0.25;
c1.anchor = GridBagConstraints.EAST;
GridBagConstraints c2 = new GridBagConstraints();
c2.gridx = 2;
c2.weightx = 0.5;
c2.anchor = GridBagConstraints.EAST;
c1.gridy = c2.gridy = 0;
fundingRatesPane.add(new JLabel(strings.getString("budgetdlg.funding_level_hdr")), c1);
fundingRatesPane.add(new JLabel(strings.getString("budgetdlg.allocation_hdr")), c2);
c0.gridy = c1.gridy = c2.gridy = 1;
fundingRatesPane.add(new JLabel(strings.getString("budgetdlg.road_fund")), c0);
fundingRatesPane.add(roadFundEntry, c1);
fundingRatesPane.add(roadFundAlloc, c2);
c0.gridy = c1.gridy = c2.gridy = 2;
fundingRatesPane.add(new JLabel(strings.getString("budgetdlg.police_fund")), c0);
fundingRatesPane.add(policeFundEntry, c1);
fundingRatesPane.add(policeFundAlloc, c2);
c0.gridy = c1.gridy = c2.gridy = 3;
fundingRatesPane.add(new JLabel(strings.getString("budgetdlg.fire_fund")), c0);
fundingRatesPane.add(fireFundEntry, c1);
fundingRatesPane.add(fireFundAlloc, c2);
JSeparator sep1 = new JSeparator(SwingConstants.HORIZONTAL);
mainBox.add(sep1);
JPanel balancePane = new JPanel(new GridBagLayout());
balancePane.setBorder(BorderFactory.createEmptyBorder(8,24,8,24));
mainBox.add(balancePane);
makeBalancePane(balancePane);
JSeparator sep2 = new JSeparator(SwingConstants.HORIZONTAL);
mainBox.add(sep2);
JPanel optionsPane = new JPanel(new GridBagLayout());
optionsPane.setBorder(BorderFactory.createEmptyBorder(8,0,0,0));
mainBox.add(optionsPane);
c0.anchor = c1.anchor = GridBagConstraints.WEST;
c0.gridy = c1.gridy = 0;
c0.weightx = c1.weightx = 0.5;
optionsPane.add(autoBudgetBtn, c0);
optionsPane.add(pauseBtn, c1);
autoBudgetBtn.setSelected(engine.autoBudget);
pauseBtn.setSelected(engine.simSpeed == Speed.PAUSED);
JPanel buttonPane = new JPanel();
add(buttonPane, BorderLayout.SOUTH);
JButton continueBtn = new JButton(strings.getString("budgetdlg.continue"));
continueBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
onContinueClicked();
}});
buttonPane.add(continueBtn);
JButton resetBtn = new JButton(strings.getString("budgetdlg.reset"));
resetBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
onResetClicked();
}});
buttonPane.add(resetBtn);
loadBudgetNumbers(true);
setAutoRequestFocus(false);
pack();
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setLocationRelativeTo(owner);
}
private JComponent makeTaxPane()
{
JPanel pane = new JPanel(new GridBagLayout());
pane.setBorder(BorderFactory.createEmptyBorder(0,0,8,0));
GridBagConstraints c0 = new GridBagConstraints();
GridBagConstraints c1 = new GridBagConstraints();
GridBagConstraints c2 = new GridBagConstraints();
c0.gridx = 0;
c0.anchor = GridBagConstraints.WEST;
c0.weightx = 0.25;
c1.gridx = 1;
c1.anchor = GridBagConstraints.EAST;
c1.weightx = 0.25;
c2.gridx = 2;
c2.anchor = GridBagConstraints.EAST;
c2.weightx = 0.5;
c0.gridy = c1.gridy = c2.gridy = 0;
pane.add(new JLabel(strings.getString("budgetdlg.tax_rate_hdr")), c1);
pane.add(new JLabel(strings.getString("budgetdlg.annual_receipts_hdr")), c2);
c0.gridy = c1.gridy = c2.gridy = 1;
pane.add(new JLabel(strings.getString("budgetdlg.tax_revenue")), c0);
pane.add(taxRateEntry, c1);
pane.add(taxRevenueLbl, c2);
return pane;
}
private void onContinueClicked()
{
if (autoBudgetBtn.isSelected() != engine.autoBudget) {
engine.toggleAutoBudget();
}
if (pauseBtn.isSelected() && engine.simSpeed != Speed.PAUSED) {
engine.setSpeed(Speed.PAUSED);
}
else if (!pauseBtn.isSelected() && engine.simSpeed == Speed.PAUSED) {
engine.setSpeed(Speed.NORMAL);
}
dispose();
}
private void onResetClicked()
{
engine.cityTax = this.origTaxRate;
engine.roadPercent = this.origRoadPct;
engine.firePercent = this.origFirePct;
engine.policePercent = this.origPolicePct;
loadBudgetNumbers(true);
}
private JComponent makeBalancePane(JPanel balancePane)
{
GridBagConstraints c0 = new GridBagConstraints();
GridBagConstraints c1 = new GridBagConstraints();
c0.anchor = GridBagConstraints.WEST;
c0.weightx = 0.5;
c0.gridx = 0;
c0.gridy = 0;
JLabel thLbl = new JLabel(strings.getString("budgetdlg.period_ending"));
Font origFont = thLbl.getFont();
Font headFont = origFont.deriveFont(Font.ITALIC);
thLbl.setFont(headFont);
thLbl.setForeground(Color.MAGENTA);
balancePane.add(thLbl, c0);
c0.gridy++;
balancePane.add(new JLabel(strings.getString("budgetdlg.cash_begin")), c0);
c0.gridy++;
balancePane.add(new JLabel(strings.getString("budgetdlg.taxes_collected")), c0);
c0.gridy++;
balancePane.add(new JLabel(strings.getString("budgetdlg.capital_expenses")), c0);
c0.gridy++;
balancePane.add(new JLabel(strings.getString("budgetdlg.operating_expenses")), c0);
c0.gridy++;
balancePane.add(new JLabel(strings.getString("budgetdlg.cash_end")), c0);
c1.anchor = GridBagConstraints.EAST;
c1.weightx = 0.25;
c1.gridx = 0;
for (int i = 0; i < 2; i++) {
if (i + 1 >= engine.financialHistory.size()) {
break;
}
Micropolis.FinancialHistory f = engine.financialHistory.get(i);
Micropolis.FinancialHistory fPrior = engine.financialHistory.get(i+1);
int cashFlow = f.totalFunds - fPrior.totalFunds;
int capExpenses = -(cashFlow - f.taxIncome + f.operatingExpenses);
c1.gridx++;
c1.gridy = 0;
thLbl = new JLabel(formatGameDate(f.cityTime-1));
thLbl.setFont(headFont);
thLbl.setForeground(Color.MAGENTA);
balancePane.add(thLbl, c1);
c1.gridy++;
JLabel previousBalanceLbl = new JLabel();
previousBalanceLbl.setText(formatFunds(fPrior.totalFunds));
balancePane.add(previousBalanceLbl, c1);
c1.gridy++;
JLabel taxIncomeLbl = new JLabel();
taxIncomeLbl.setText(formatFunds(f.taxIncome));
balancePane.add(taxIncomeLbl, c1);
c1.gridy++;
JLabel capExpensesLbl = new JLabel();
capExpensesLbl.setText(formatFunds(capExpenses));
balancePane.add(capExpensesLbl, c1);
c1.gridy++;
JLabel opExpensesLbl = new JLabel();
opExpensesLbl.setText(formatFunds(f.operatingExpenses));
balancePane.add(opExpensesLbl, c1);
c1.gridy++;
JLabel newBalanceLbl = new JLabel();
newBalanceLbl.setText(formatFunds(f.totalFunds));
balancePane.add(newBalanceLbl, c1);
}
return balancePane;
}
}

View file

@ -0,0 +1,35 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.gui;
import java.awt.Color;
public class ColorParser
{
private ColorParser() {}
static Color parseColor(String str)
{
if (str.startsWith("#") && str.length() == 7) {
return new Color(Integer.parseInt(str.substring(1), 16));
}
else if (str.startsWith("rgba(") && str.endsWith(")")) {
String [] parts = str.substring(5,str.length()-1).split(",");
int r = Integer.parseInt(parts[0]);
int g = Integer.parseInt(parts[1]);
int b = Integer.parseInt(parts[2]);
double aa = Double.parseDouble(parts[3]);
int a = Math.min(255, (int)Math.floor(aa*256.0));
return new Color(r,g,b,a);
}
else {
throw new Error("invalid color format: "+str);
}
}
}

View file

@ -0,0 +1,146 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.gui;
import java.awt.*;
import java.awt.image.*;
import java.net.URL;
import javax.swing.*;
import micropolisj.engine.*;
public class DemandIndicator extends JComponent
implements Micropolis.Listener
{
Micropolis engine;
public DemandIndicator()
{
}
public void setEngine(Micropolis newEngine)
{
if (engine != null) { //old engine
engine.removeListener(this);
}
engine = newEngine;
if (engine != null) { //new engine
engine.addListener(this);
}
repaint();
}
static final BufferedImage backgroundImage = loadImage("/demandg.png");
static BufferedImage loadImage(String resourceName)
{
URL iconUrl = MicropolisDrawingArea.class.getResource(resourceName);
Image refImage = new ImageIcon(iconUrl).getImage();
BufferedImage bi = new BufferedImage(refImage.getWidth(null), refImage.getHeight(null),
BufferedImage.TYPE_INT_RGB);
Graphics2D gr = bi.createGraphics();
gr.drawImage(refImage, 0, 0, null);
return bi;
}
static final Dimension MY_SIZE = new Dimension(
backgroundImage.getWidth(),
backgroundImage.getHeight()
);
@Override
public Dimension getMinimumSize()
{
return MY_SIZE;
}
@Override
public Dimension getPreferredSize()
{
return MY_SIZE;
}
@Override
public Dimension getMaximumSize()
{
return MY_SIZE;
}
static final int UPPER_EDGE = 19;
static final int LOWER_EDGE = 28;
static final int MAX_LENGTH = 16;
public void paintComponent(Graphics gr1)
{
Graphics2D gr = (Graphics2D) gr1;
gr.drawImage(backgroundImage, 0, 0, null);
if (engine == null)
return;
int resValve = engine.getResValve();
int ry0 = resValve <= 0 ? LOWER_EDGE : UPPER_EDGE;
int ry1 = ry0 - resValve/100;
if (ry1 - ry0 > MAX_LENGTH) { ry1 = ry0 + MAX_LENGTH; }
if (ry1 - ry0 < -MAX_LENGTH) { ry1 = ry0 - MAX_LENGTH; }
int comValve = engine.getComValve();
int cy0 = comValve <= 0 ? LOWER_EDGE : UPPER_EDGE;
int cy1 = cy0 - comValve/100;
int indValve = engine.getIndValve();
int iy0 = indValve <= 0 ? LOWER_EDGE : UPPER_EDGE;
int iy1 = iy0 - indValve/100;
if (ry0 != ry1)
{
Rectangle resRect = new Rectangle(8, Math.min(ry0,ry1), 6, Math.abs(ry1-ry0));
gr.setColor(Color.GREEN);
gr.fill(resRect);
gr.setColor(Color.BLACK);
gr.draw(resRect);
}
if (cy0 != cy1)
{
Rectangle comRect = new Rectangle(17, Math.min(cy0,cy1), 6, Math.abs(cy1-cy0));
gr.setColor(Color.BLUE);
gr.fill(comRect);
gr.setColor(Color.BLACK);
gr.draw(comRect);
}
if (iy0 != iy1)
{
Rectangle indRect = new Rectangle(26, Math.min(iy0,iy1), 6, Math.abs(iy1-iy0));
gr.setColor(Color.YELLOW);
gr.fill(indRect);
gr.setColor(Color.BLACK);
gr.draw(indRect);
}
}
//implements Micropolis.Listener
public void demandChanged()
{
repaint();
}
//implements Micropolis.Listener
public void cityMessage(MicropolisMessage m, CityLocation p, boolean x) { }
public void citySound(Sound sound, CityLocation p) { }
public void censusChanged() { }
public void evaluationChanged() { }
public void fundsChanged() { }
public void optionsChanged() { }
}

View file

@ -0,0 +1,298 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.gui;
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
import micropolisj.engine.*;
import static micropolisj.gui.MainWindow.formatFunds;
public class EvaluationPane extends JPanel
implements Micropolis.Listener
{
Micropolis engine;
JLabel yesLbl;
JLabel noLbl;
JLabel [] voterProblemLbl;
JLabel [] voterCountLbl;
JLabel popLbl;
JLabel deltaLbl;
JLabel assessLbl;
JLabel cityClassLbl;
JLabel gameLevelLbl;
JLabel scoreLbl;
JLabel scoreDeltaLbl;
static ResourceBundle cstrings = ResourceBundle.getBundle("micropolisj.CityStrings");
static ResourceBundle gstrings = MainWindow.strings;
public EvaluationPane(Micropolis _engine)
{
super(new BorderLayout());
JButton dismissBtn = new JButton(gstrings.getString("dismiss-evaluation"));
dismissBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
onDismissClicked();
}});
add(dismissBtn, BorderLayout.SOUTH);
Box b1 = new Box(BoxLayout.X_AXIS);
add(b1, BorderLayout.CENTER);
b1.add(makePublicOpinionPane());
b1.add(new JSeparator(SwingConstants.VERTICAL));
b1.add(makeStatisticsPane());
assert _engine != null;
setEngine(_engine);
}
public void setEngine(Micropolis newEngine)
{
if (engine != null) { //old engine
engine.removeListener(this);
}
engine = newEngine;
if (engine != null) { //new engine
engine.addListener(this);
loadEvaluation();
}
}
private void onDismissClicked()
{
setVisible(false);
}
private JComponent makePublicOpinionPane()
{
JPanel me = new JPanel(new GridBagLayout());
GridBagConstraints c1 = new GridBagConstraints();
GridBagConstraints c2 = new GridBagConstraints();
GridBagConstraints c3 = new GridBagConstraints();
// c1 is for the full-width headers
c1.gridx = c1.gridy = 0;
c1.gridwidth = 2;
c1.gridheight = 1;
c1.weightx = 1.0;
c1.fill = GridBagConstraints.NONE;
c1.anchor = GridBagConstraints.NORTH;
JLabel headerLbl = new JLabel(gstrings.getString("public-opinion"));
Font curFont = headerLbl.getFont();
headerLbl.setFont(
curFont.deriveFont(curFont.getStyle() | Font.BOLD, (float)(curFont.getSize() * 1.2))
);
me.add(headerLbl, c1);
c1.gridy = 1;
c1.insets = new Insets(3, 0, 3, 0);
me.add(new JLabel(gstrings.getString("public-opinion-1")), c1);
c1.gridy = 4;
me.add(new JLabel(gstrings.getString("public-opinion-2")), c1);
c2.gridx = 0;
c2.gridy = 2;
c2.gridwidth = c2.gridheight = 1;
c2.weightx = 1.0;
c2.anchor = GridBagConstraints.EAST;
c2.insets = new Insets(0, 0, 0, 4);
me.add(new JLabel(gstrings.getString("public-opinion-yes")), c2);
c2.gridy = 3;
me.add(new JLabel(gstrings.getString("public-opinion-no")), c2);
c3.gridx = 1;
c3.gridwidth = c3.gridheight = 1;
c3.weightx = 1.0;
c3.anchor = GridBagConstraints.WEST;
c3.insets = new Insets(0, 4, 0, 0);
c3.gridy = 2;
yesLbl = new JLabel();
me.add(yesLbl, c3);
c3.gridy = 3;
noLbl = new JLabel();
me.add(noLbl, c3);
c2.gridy = c3.gridy = 5;
final int NUM_PROBS = 4;
voterProblemLbl = new JLabel[NUM_PROBS];
voterCountLbl = new JLabel[NUM_PROBS];
for (int i = 0; i < NUM_PROBS; i++) {
voterProblemLbl[i] = new JLabel();
me.add(voterProblemLbl[i], c2);
voterCountLbl[i] = new JLabel();
me.add(voterCountLbl[i], c3);
c2.gridy = ++c3.gridy;
}
// add glue so that everything will align towards the top
c1.gridy = 999;
c1.weighty = 1.0;
me.add(new JLabel(), c1);
return me;
}
private JComponent makeStatisticsPane()
{
JPanel me = new JPanel(new GridBagLayout());
GridBagConstraints c1 = new GridBagConstraints();
GridBagConstraints c2 = new GridBagConstraints();
GridBagConstraints c3 = new GridBagConstraints();
c1.gridx = c1.gridy = 0;
c1.gridwidth = 2;
c1.gridheight = 1;
c1.weightx = 1.0;
c1.fill = GridBagConstraints.NONE;
c1.anchor = GridBagConstraints.NORTH;
c1.insets = new Insets(0,0,3,0);
JLabel headerLbl = new JLabel(gstrings.getString("statistics-head"));
Font curFont = headerLbl.getFont();
headerLbl.setFont(
curFont.deriveFont(curFont.getStyle() | Font.BOLD, (float)(curFont.getSize() * 1.2))
);
me.add(headerLbl, c1);
c1.gridy = 20;
c1.insets = new Insets(9, 0, 3, 0);
c1.fill = GridBagConstraints.VERTICAL;
JLabel header2Lbl = new JLabel(gstrings.getString("city-score-head"));
me.add(header2Lbl, c1);
c2.gridx = 0;
c2.gridwidth = c2.gridheight = 1;
c2.weightx = 0.5;
c2.anchor = GridBagConstraints.EAST;
c2.insets = new Insets(0, 0, 0, 4);
c3.gridx = 1;
c3.gridwidth = c3.gridheight = 1;
c3.weightx = 0.5;
c3.anchor = GridBagConstraints.WEST;
c3.insets = new Insets(0, 4, 0, 0);
c2.gridy = c3.gridy = 1;
me.add(new JLabel(gstrings.getString("stats-population")), c2);
popLbl = new JLabel();
me.add(popLbl, c3);
c2.gridy = ++c3.gridy;
me.add(new JLabel(gstrings.getString("stats-net-migration")), c2);
deltaLbl = new JLabel();
me.add(deltaLbl, c3);
c2.gridy = ++c3.gridy;
me.add(new JLabel(gstrings.getString("stats-last-year")), c2);
c2.gridy = ++c3.gridy;
me.add(new JLabel(gstrings.getString("stats-assessed-value")), c2);
assessLbl = new JLabel();
me.add(assessLbl, c3);
c2.gridy = ++c3.gridy;
me.add(new JLabel(gstrings.getString("stats-category")), c2);
cityClassLbl = new JLabel();
me.add(cityClassLbl, c3);
c2.gridy = ++c3.gridy;
me.add(new JLabel(gstrings.getString("stats-game-level")), c2);
gameLevelLbl = new JLabel();
me.add(gameLevelLbl, c3);
c2.gridy = c3.gridy = 21;
me.add(new JLabel(gstrings.getString("city-score-current")), c2);
scoreLbl = new JLabel();
me.add(scoreLbl, c3);
c2.gridy = ++c3.gridy;
me.add(new JLabel(gstrings.getString("city-score-change")), c2);
scoreDeltaLbl = new JLabel();
me.add(scoreDeltaLbl, c3);
// add glue so that everything will align towards the top
c1.gridy = 999;
c1.weighty = 1.0;
c1.insets = new Insets(0,0,0,0);
me.add(new JLabel(), c1);
return me;
}
//implements Micropolis.Listener
public void cityMessage(MicropolisMessage message, CityLocation loc, boolean isPic) {}
public void citySound(Sound sound, CityLocation loc) {}
public void censusChanged() {}
public void demandChanged() {}
public void fundsChanged() {}
public void optionsChanged() {}
//implements Micropolis.Listener
public void evaluationChanged()
{
loadEvaluation();
}
private void loadEvaluation()
{
NumberFormat pctFmt = NumberFormat.getPercentInstance();
yesLbl.setText(pctFmt.format(0.01 * engine.evaluation.cityYes));
noLbl.setText(pctFmt.format(0.01 * engine.evaluation.cityNo));
for (int i = 0; i < voterProblemLbl.length; i++) {
CityProblem p = i < engine.evaluation.problemOrder.length ? engine.evaluation.problemOrder[i] : null;
int numVotes = p != null ? engine.evaluation.problemVotes.get(p) : 0;
if (numVotes != 0) {
voterProblemLbl[i].setText(cstrings.getString("problem."+p.name()));
voterCountLbl[i].setText(pctFmt.format(0.01 * numVotes));
voterProblemLbl[i].setVisible(true);
voterCountLbl[i].setVisible(true);
} else {
voterProblemLbl[i].setVisible(false);
voterCountLbl[i].setVisible(false);
}
}
NumberFormat nf = NumberFormat.getInstance();
popLbl.setText(nf.format(engine.evaluation.cityPop));
deltaLbl.setText(nf.format(engine.evaluation.deltaCityPop));
assessLbl.setText(formatFunds(engine.evaluation.cityAssValue));
cityClassLbl.setText(getCityClassName(engine.evaluation.cityClass));
gameLevelLbl.setText(getGameLevelName(engine.gameLevel));
scoreLbl.setText(nf.format(engine.evaluation.cityScore));
scoreDeltaLbl.setText(nf.format(engine.evaluation.deltaCityScore));
}
static String getCityClassName(int cityClass)
{
return cstrings.getString("class."+cityClass);
}
static String getGameLevelName(int gameLevel)
{
return cstrings.getString("level."+gameLevel);
}
}

View file

@ -0,0 +1,320 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.gui;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Path2D;
import java.text.*;
import java.util.*;
import javax.swing.*;
import micropolisj.engine.*;
import static micropolisj.gui.ColorParser.parseColor;
public class GraphsPane extends JPanel
implements Micropolis.Listener
{
Micropolis engine;
JToggleButton tenYearsBtn;
JToggleButton onetwentyYearsBtn;
GraphArea graphArea;
static enum TimePeriod
{
TEN_YEARS,
ONETWENTY_YEARS;
}
static enum GraphData
{
RESPOP,
COMPOP,
INDPOP,
MONEY,
CRIME,
POLLUTION;
}
EnumMap<GraphData,JToggleButton> dataBtns = new EnumMap<>(GraphData.class);
static ResourceBundle strings = MainWindow.strings;
static final int LEFT_MARGIN = 4;
static final int RIGHT_MARGIN = 4;
static final int TOP_MARGIN = 2;
static final int BOTTOM_MARGIN = 2;
static final int LEGEND_PADDING = 6;
public GraphsPane(Micropolis engine)
{
super(new BorderLayout());
assert engine != null;
this.engine = engine;
engine.addListener(this);
JButton dismissBtn = new JButton(strings.getString("dismiss_graph"));
dismissBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
onDismissClicked();
}});
add(dismissBtn, BorderLayout.SOUTH);
JPanel b1 = new JPanel(new BorderLayout());
add(b1, BorderLayout.CENTER);
JPanel toolsPane = new JPanel(new GridBagLayout());
b1.add(toolsPane, BorderLayout.WEST);
GridBagConstraints c = new GridBagConstraints();
c.gridx = c.gridy = 0;
c.gridwidth = 2;
c.fill = GridBagConstraints.BOTH;
c.insets = new Insets(1,1,1,1);
tenYearsBtn = new JToggleButton(strings.getString("ten_years"));
tenYearsBtn.setMargin(new Insets(0,0,0,0));
tenYearsBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
setTimePeriod(TimePeriod.TEN_YEARS);
}});
toolsPane.add(tenYearsBtn, c);
c.gridy++;
onetwentyYearsBtn = new JToggleButton(strings.getString("onetwenty_years"));
onetwentyYearsBtn.setMargin(new Insets(0,0,0,0));
onetwentyYearsBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
setTimePeriod(TimePeriod.ONETWENTY_YEARS);
}});
toolsPane.add(onetwentyYearsBtn, c);
c.gridx = 0;
c.gridy = 2;
c.gridwidth = 1;
c.anchor = GridBagConstraints.NORTH;
c.weightx = 0.5;
toolsPane.add(makeDataBtn(GraphData.RESPOP), c);
c.gridy = 3;
toolsPane.add(makeDataBtn(GraphData.COMPOP), c);
c.gridy = 4;
toolsPane.add(makeDataBtn(GraphData.INDPOP), c);
c.gridx = 1;
c.gridy = 2;
toolsPane.add(makeDataBtn(GraphData.MONEY), c);
c.gridy = 3;
toolsPane.add(makeDataBtn(GraphData.CRIME), c);
c.gridy = 4;
toolsPane.add(makeDataBtn(GraphData.POLLUTION), c);
graphArea = new GraphArea();
b1.add(graphArea, BorderLayout.CENTER);
setTimePeriod(TimePeriod.TEN_YEARS);
dataBtns.get(GraphData.MONEY).setSelected(true);
dataBtns.get(GraphData.POLLUTION).setSelected(true);
}
public void setEngine(Micropolis newEngine)
{
if (engine != null) { //old engine
engine.removeListener(this);
}
engine = newEngine;
if (engine != null) { //new engine
engine.addListener(this);
graphArea.repaint();
}
}
private void onDismissClicked()
{
setVisible(false);
}
//implements Micropolis.Listener
public void cityMessage(MicropolisMessage message, CityLocation loc, boolean isPic) {}
public void citySound(Sound sound, CityLocation loc) {}
public void demandChanged() {}
public void evaluationChanged() {}
public void fundsChanged() {}
public void optionsChanged() {}
//implements Micropolis.Listener
public void censusChanged()
{
graphArea.repaint();
}
private JToggleButton makeDataBtn(GraphData graph)
{
String icon1name = strings.getString("graph_button."+graph.name());
String icon2name = strings.getString("graph_button."+graph.name()+".selected");
ImageIcon icon1 = new ImageIcon(getClass().getResource("/"+icon1name));
ImageIcon icon2 = new ImageIcon(getClass().getResource("/"+icon2name));
JToggleButton btn = new JToggleButton();
btn.setIcon(icon1);
btn.setSelectedIcon(icon2);
btn.setBorder(null);
btn.setBorderPainted(false);
btn.setFocusPainted(false);
btn.setContentAreaFilled(false);
btn.setMargin(new Insets(0,0,0,0));
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
graphArea.repaint();
}});
dataBtns.put(graph, btn);
return btn;
}
int getHistoryValue(GraphData graph, int pos)
{
assert pos >= 0 && pos < 240;
switch(graph) {
case RESPOP: return engine.history.res[pos];
case COMPOP: return engine.history.com[pos];
case INDPOP: return engine.history.ind[pos];
case MONEY: return engine.history.money[pos];
case CRIME: return engine.history.crime[pos];
case POLLUTION: return engine.history.pollution[pos];
default: throw new Error("unexpected");
}
}
void setTimePeriod(TimePeriod period)
{
tenYearsBtn.setSelected(period == TimePeriod.TEN_YEARS);
onetwentyYearsBtn.setSelected(period == TimePeriod.ONETWENTY_YEARS);
graphArea.repaint();
}
class GraphArea extends JComponent
{
GraphArea()
{
setBorder(BorderFactory.createLoweredBevelBorder());
}
@Override
public void paintComponent(Graphics gr1)
{
Graphics2D gr = (Graphics2D)gr1;
FontMetrics fm = gr.getFontMetrics();
gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gr.setColor(Color.WHITE);
gr.fill(gr.getClipBounds());
// determine length of longest label
int maxLabelWidth = 0;
for (GraphData gd : GraphData.values())
{
String labelStr = strings.getString("graph_label."+gd.name());
int adv = fm.stringWidth(labelStr);
if (adv > maxLabelWidth) {
maxLabelWidth = adv;
}
}
int leftEdge = getInsets().left + LEFT_MARGIN;
int topEdge = getInsets().top + TOP_MARGIN + fm.getHeight()*2;
int bottomEdge = getHeight() - getInsets().bottom - getInsets().top - BOTTOM_MARGIN;
int rightEdge = getWidth() - getInsets().right - getInsets().left - RIGHT_MARGIN - maxLabelWidth - LEGEND_PADDING;
// draw graph lower, upper borders
gr.setColor(Color.BLACK);
gr.drawLine(leftEdge,topEdge,rightEdge,topEdge);
gr.drawLine(leftEdge,bottomEdge,rightEdge,bottomEdge);
// draw vertical bars and label the dates
boolean isOneTwenty = onetwentyYearsBtn.isSelected();
int unitPeriod = isOneTwenty ? 12*Micropolis.CENSUSRATE : Micropolis.CENSUSRATE;
int hashPeriod = isOneTwenty ? 10*unitPeriod : 12*unitPeriod;
int startTime = ((engine.history.cityTime / unitPeriod) - 119) * unitPeriod;
double x_interval = (rightEdge - leftEdge) / 120.0;
for (int i = 0; i < 120; i++) {
int t = startTime + i * unitPeriod; // t might be negative
if (t % hashPeriod == 0) {
// year
int year = 1900+(t/(12*Micropolis.CENSUSRATE));
int numHashes = t/hashPeriod;
int x = (int)Math.round(leftEdge+i*x_interval);
int y = getInsets().top + TOP_MARGIN +
(numHashes % 2 == 0 ? fm.getHeight() : 0) +
fm.getAscent();
gr.drawString(Integer.toString(year), x, y);
gr.drawLine(x,topEdge,x,bottomEdge);
}
}
int H = isOneTwenty ? 239 : 119;
final HashMap<GraphData, Path2D.Double> paths = new HashMap<>();
for (GraphData gd : GraphData.values())
{
if (dataBtns.get(gd).isSelected()) {
Path2D.Double path = new Path2D.Double();
for (int i = 0; i < 120; i++) {
double xp = leftEdge + i * x_interval;
double yp = bottomEdge - getHistoryValue(gd,H-i) * (bottomEdge-topEdge) / 256.0;
if (i == 0) {
path.moveTo(xp, yp);
} else {
path.lineTo(xp, yp);
}
}
paths.put(gd, path);
}
}
GraphData [] myGraphs = paths.keySet().toArray(new GraphData[0]);
Arrays.sort(myGraphs, new Comparator<GraphData>() {
public int compare(GraphData a, GraphData b) {
double y0 = paths.get(a).getCurrentPoint().getY();
double y1 = paths.get(b).getCurrentPoint().getY();
return -Double.compare(y0,y1);
}});
int lbottom = bottomEdge;
for (GraphData gd : myGraphs)
{
String labelStr = strings.getString("graph_label."+gd.name());
String colStr = strings.getString("graph_color."+gd.name());
Color col = parseColor(colStr);
Path2D.Double path = paths.get(gd);
gr.setColor(col);
gr.setStroke(new BasicStroke(2));
gr.draw(path);
int x = rightEdge + LEGEND_PADDING;
int y = (int)Math.round(path.getCurrentPoint().getY()+fm.getAscent()/2);
y = Math.min(lbottom, y);
lbottom = y - fm.getAscent();
gr.setColor(col);
gr.drawString(labelStr, x-1, y);
gr.drawString(labelStr, x, y-1);
gr.setColor(Color.BLACK);
gr.drawString(labelStr, x, y);
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,44 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.gui;
import java.util.*;
import javax.swing.*;
import javax.swing.text.*;
import micropolisj.engine.*;
public class MessagesPane extends JTextPane
{
static ResourceBundle cityMessageStrings = ResourceBundle.getBundle("micropolisj.CityMessages");
public MessagesPane()
{
setEditable(false);
}
public void appendCityMessage(MicropolisMessage message)
{
appendMessageText(cityMessageStrings.getString(message.name()));
}
void appendMessageText(String messageText)
{
try {
StyledDocument doc = getStyledDocument();
if (doc.getLength() != 0) {
doc.insertString(doc.getLength(), "\n", null);
}
doc.insertString(doc.getLength(), messageText, null);
}
catch (BadLocationException e) {
throw new Error("unexpected", e);
}
}
}

View file

@ -0,0 +1,379 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.gui;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.net.URL;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.Timer;
import micropolisj.engine.*;
import static micropolisj.engine.TileConstants.*;
public class MicropolisDrawingArea extends JComponent
implements Scrollable, MapListener
{
Micropolis m;
boolean blinkUnpoweredZones = true;
HashSet<Point> unpoweredZones = new HashSet<>();
boolean blink;
Timer blinkTimer;
ToolPreview toolPreview;
int shakeStep;
static final Dimension PREFERRED_VIEWPORT_SIZE = new Dimension(640,640);
public MicropolisDrawingArea(Micropolis engine)
{
this.m = engine;
m.addMapListener(this);
addAncestorListener(new AncestorListener() {
public void ancestorAdded(AncestorEvent evt) {
startBlinkTimer();
}
public void ancestorRemoved(AncestorEvent evt) {
stopBlinkTimer();
}
public void ancestorMoved(AncestorEvent evt) {}
});
}
@Override
public Dimension getPreferredSize()
{
assert this.m != null;
return new Dimension(TILE_WIDTH*m.getWidth(),TILE_HEIGHT*m.getHeight());
}
public void setEngine(Micropolis newEngine)
{
assert newEngine != null;
if (this.m != null) { //old engine
this.m.removeMapListener(this);
}
this.m = newEngine;
if (this.m != null) { //new engine
this.m.addMapListener(this);
}
// size may have changed
invalidate();
repaint();
}
static Image [] tileImages = loadTileImages("/tiles.png");
public static final int TILE_WIDTH = 16;
public static final int TILE_HEIGHT = 16;
static Image [] loadTileImages(String resourceName)
{
URL iconUrl = MicropolisDrawingArea.class.getResource(resourceName);
Image refImage = new ImageIcon(iconUrl).getImage();
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice dev = env.getDefaultScreenDevice();
GraphicsConfiguration conf = dev.getDefaultConfiguration();
Image [] images = new Image[refImage.getHeight(null) / TILE_HEIGHT];
for (int i = 0; i < images.length; i++)
{
BufferedImage bi = conf.createCompatibleImage(TILE_WIDTH, TILE_HEIGHT, Transparency.OPAQUE);
Graphics2D gr = bi.createGraphics();
gr.drawImage(refImage, 0, 0, TILE_WIDTH, TILE_HEIGHT,
0, i * TILE_HEIGHT,
TILE_WIDTH, (i+1)*TILE_HEIGHT,
null);
images[i] = bi;
}
return images;
}
static Map<SpriteKind, Map<Integer, Image> > spriteImages;
static {
spriteImages = new EnumMap<SpriteKind, Map<Integer,Image> >(SpriteKind.class);
for (SpriteKind kind : SpriteKind.values())
{
HashMap<Integer,Image> imgs = new HashMap<>();
for (int i = 0; i < kind.numFrames; i++) {
Image img = loadSpriteImage(kind, i);
if (img != null) {
imgs.put(i, img);
}
}
spriteImages.put(kind, imgs);
}
}
static Image loadSpriteImage(SpriteKind kind, int frameNo)
{
String resourceName = "/obj"+kind.objectId+"-"+frameNo+".png";
URL iconUrl = MicropolisDrawingArea.class.getResource(resourceName);
if (iconUrl == null)
return null;
return new ImageIcon(iconUrl).getImage();
}
void drawSprite(Graphics gr, Sprite sprite)
{
assert sprite.isVisible();
Image img = spriteImages.get(sprite.kind).get(sprite.frame-1);
if (img != null) {
gr.drawImage(img, sprite.x + sprite.offx, sprite.y + sprite.offy, null);
}
else {
gr.setColor(Color.RED);
gr.fillRect(sprite.x, sprite.y, 16, 16);
gr.setColor(Color.WHITE);
gr.drawString(Integer.toString(sprite.frame-1),sprite.x,sprite.y);
}
}
public void paintComponent(Graphics gr)
{
final int width = m.getWidth();
final int height = m.getHeight();
Rectangle clipRect = gr.getClipBounds();
int minX = Math.max(0, clipRect.x / TILE_WIDTH);
int minY = Math.max(0, clipRect.y / TILE_HEIGHT);
int maxX = Math.min(width, 1 + (clipRect.x + clipRect.width-1) / TILE_WIDTH);
int maxY = Math.min(height, 1 + (clipRect.y + clipRect.height-1) / TILE_HEIGHT);
for (int y = minY; y < maxY; y++)
{
for (int x = minX; x < maxX; x++)
{
int cell = m.getTile(x,y);
int tile = (cell & LOMASK) % tileImages.length;
if (blinkUnpoweredZones &&
(cell & ZONEBIT) != 0 &&
(cell & PWRBIT) == 0)
{
unpoweredZones.add(new Point(x,y));
if (blink)
tile = LIGHTNINGBOLT;
}
gr.drawImage(tileImages[tile],
x*TILE_WIDTH + (shakeStep != 0 ? getShakeModifier(y) : 0),
y*TILE_HEIGHT,
null);
}
}
for (Sprite sprite : m.allSprites())
{
if (sprite.isVisible())
{
drawSprite(gr, sprite);
}
}
if (toolPreview != null)
{
int x0 = toolPreview.rect.x * TILE_WIDTH;
int x1 = (toolPreview.rect.x + toolPreview.rect.width) * TILE_WIDTH;
int y0 = toolPreview.rect.y * TILE_HEIGHT;
int y1 = (toolPreview.rect.y + toolPreview.rect.height) * TILE_HEIGHT;
gr.setColor(Color.BLACK);
gr.drawLine(x0-1,y0-1,x0-1,y1-1);
gr.drawLine(x0-1,y0-1,x1-1,y0-1);
gr.drawLine(x1+3,y0-3,x1+3,y1+3);
gr.drawLine(x0-3,y1+3,x1+3,y1+3);
gr.setColor(Color.WHITE);
gr.drawLine(x0-4,y0-4,x1+3,y0-4);
gr.drawLine(x0-4,y0-4,x0-4,y1+3);
gr.drawLine(x1, y0-1,x1, y1 );
gr.drawLine(x0-1,y1, x1, y1 );
gr.setColor(toolPreview.borderColor);
gr.drawRect(x0-3,y0-3,x1-x0+5,y1-y0+5);
gr.drawRect(x0-2,y0-2,x1-x0+3,y1-y0+3);
if (toolPreview.fillColor != null) {
gr.setColor(toolPreview.fillColor);
gr.fillRect(x0,y0,x1-x0,y1-y0);
}
}
}
static class ToolPreview
{
Rectangle rect;
Color borderColor;
Color fillColor;
}
public void setToolPreview(Rectangle newRect, Color toolColor)
{
ToolPreview tp = new ToolPreview();
tp.rect = newRect;
tp.borderColor = toolColor;
setToolPreview(tp);
}
public void setToolPreview(ToolPreview newPreview)
{
if (toolPreview == newPreview)
return;
if (toolPreview != null && toolPreview.equals(newPreview))
return;
if (toolPreview != null)
{
repaint(new Rectangle(
toolPreview.rect.x*TILE_WIDTH - 4,
toolPreview.rect.y*TILE_HEIGHT - 4,
toolPreview.rect.width*TILE_WIDTH + 8,
toolPreview.rect.height*TILE_HEIGHT + 8
));
}
toolPreview = newPreview;
if (toolPreview != null)
{
repaint(new Rectangle(
toolPreview.rect.x*TILE_WIDTH - 4,
toolPreview.rect.y*TILE_HEIGHT - 4,
toolPreview.rect.width*TILE_WIDTH + 8,
toolPreview.rect.height*TILE_HEIGHT + 8
));
}
}
//implements Scrollable
public Dimension getPreferredScrollableViewportSize()
{
return PREFERRED_VIEWPORT_SIZE;
}
//implements Scrollable
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)
{
if (orientation == SwingConstants.VERTICAL)
return visibleRect.height;
else
return visibleRect.width;
}
//implements Scrollable
public boolean getScrollableTracksViewportWidth()
{
return false;
}
//implements Scrollable
public boolean getScrollableTracksViewportHeight()
{
return false;
}
//implements Scrollable
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)
{
if (orientation == SwingConstants.VERTICAL)
return TILE_HEIGHT * 3;
else
return TILE_WIDTH * 3;
}
private Rectangle getSpriteBounds(Sprite sprite, int x, int y)
{
return new Rectangle(x+sprite.offx, y+sprite.offy, sprite.width, sprite.height);
}
public Rectangle getTileBounds(int xpos, int ypos)
{
return new Rectangle(xpos*TILE_WIDTH, ypos * TILE_HEIGHT,
TILE_WIDTH, TILE_HEIGHT);
}
//implements MapListener
public void mapOverlayDataChanged(MapState overlayDataType)
{
}
//implements MapListener
public void spriteMoved(Sprite sprite)
{
repaint(getSpriteBounds(sprite, sprite.lastX, sprite.lastY));
repaint(getSpriteBounds(sprite, sprite.x, sprite.y));
}
//implements MapListener
public void tileChanged(int xpos, int ypos)
{
repaint(getTileBounds(xpos, ypos));
}
//implements MapListener
public void wholeMapChanged()
{
repaint();
}
void doBlink()
{
if (!unpoweredZones.isEmpty())
{
blink = !blink;
for (Point loc : unpoweredZones)
{
repaint(getTileBounds(loc.x, loc.y));
}
unpoweredZones.clear();
}
}
void startBlinkTimer()
{
assert blinkTimer == null;
ActionListener callback = new ActionListener() {
public void actionPerformed(ActionEvent evt)
{
doBlink();
}
};
blinkTimer = new Timer(500, callback);
blinkTimer.start();
}
void stopBlinkTimer()
{
if (blinkTimer != null) {
blinkTimer.stop();
blinkTimer = null;
}
}
void shake(int i)
{
shakeStep = i;
repaint();
}
static final int SHAKE_STEPS = 40;
int getShakeModifier(int row)
{
return (int)Math.round(4.0 * Math.sin((double)(shakeStep+row/2)/2.0));
}
}

View file

@ -0,0 +1,225 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.gui;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import micropolisj.engine.*;
import static micropolisj.gui.MainWindow.EXTENSION;
public class NewCityDialog extends JDialog
{
Micropolis engine;
JButton previousMapBtn;
Stack<Micropolis> previousMaps = new Stack<Micropolis>();
Stack<Micropolis> nextMaps = new Stack<Micropolis>();
OverlayMapView mapPane;
HashMap<Integer,JRadioButton> levelBtns = new HashMap<Integer,JRadioButton>();
static final ResourceBundle strings = MainWindow.strings;
public NewCityDialog(MainWindow owner, boolean showCancelOption)
{
super(owner);
setTitle(strings.getString("welcome.caption"));
setModal(true);
assert owner != null;
JPanel p1 = new JPanel(new BorderLayout());
p1.setBorder(BorderFactory.createEmptyBorder(10,20,10,20));
getContentPane().add(p1, BorderLayout.CENTER);
engine = new Micropolis();
new MapGenerator(engine).generateNewCity();
mapPane = new OverlayMapView(engine);
mapPane.setBorder(BorderFactory.createLoweredBevelBorder());
p1.add(mapPane, BorderLayout.CENTER);
JPanel p2 = new JPanel(new BorderLayout());
p1.add(p2, BorderLayout.EAST);
Box levelBox = new Box(BoxLayout.Y_AXIS);
levelBox.setBorder(BorderFactory.createEmptyBorder(0,10,0,10));
p2.add(levelBox, BorderLayout.CENTER);
levelBox.add(Box.createVerticalGlue());
JRadioButton radioBtn;
for (int lev = GameLevel.MIN_LEVEL; lev <= GameLevel.MAX_LEVEL; lev++)
{
final int x = lev;
radioBtn = new JRadioButton(strings.getString("menu.difficulty."+lev));
radioBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
setGameLevel(x);
}});
levelBox.add(radioBtn);
levelBtns.put(lev, radioBtn);
}
levelBox.add(Box.createVerticalGlue());
setGameLevel(GameLevel.MIN_LEVEL);
JPanel buttonPane = new JPanel();
getContentPane().add(buttonPane, BorderLayout.SOUTH);
JButton btn;
btn = new JButton(strings.getString("welcome.previous_map"));
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
onPreviousMapClicked();
}});
btn.setEnabled(false);
buttonPane.add(btn);
previousMapBtn = btn;
btn = new JButton(strings.getString("welcome.play_this_map"));
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
onPlayClicked();
}});
buttonPane.add(btn);
getRootPane().setDefaultButton(btn);
btn = new JButton(strings.getString("welcome.next_map"));
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
onNextMapClicked();
}});
buttonPane.add(btn);
btn = new JButton(strings.getString("welcome.load_city"));
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
onLoadCityClicked();
}});
buttonPane.add(btn);
if (showCancelOption) {
btn = new JButton(strings.getString("welcome.cancel"));
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
onCancelClicked();
}});
buttonPane.add(btn);
}
else {
btn = new JButton(strings.getString("welcome.quit"));
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
onQuitClicked();
}});
buttonPane.add(btn);
}
pack();
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setLocationRelativeTo(owner);
}
private void onPreviousMapClicked()
{
if (previousMaps.isEmpty())
return;
nextMaps.push(engine);
engine = previousMaps.pop();
mapPane.setEngine(engine);
previousMapBtn.setEnabled(!previousMaps.isEmpty());
}
private void onNextMapClicked()
{
if (nextMaps.isEmpty())
{
Micropolis m = new Micropolis();
new MapGenerator(m).generateNewCity();
nextMaps.add(m);
}
previousMaps.push(engine);
engine = nextMaps.pop();
mapPane.setEngine(engine);
previousMapBtn.setEnabled(true);
}
private void onLoadCityClicked()
{
try
{
JFileChooser fc = new JFileChooser();
FileNameExtensionFilter filter1 = new FileNameExtensionFilter(strings.getString("cty_file"), EXTENSION);
fc.setFileFilter(filter1);
int rv = fc.showOpenDialog(this);
if (rv == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
Micropolis newEngine = new Micropolis();
newEngine.load(file);
startPlaying(newEngine, file);
}
}
catch (Exception e)
{
e.printStackTrace(System.err);
JOptionPane.showMessageDialog(this, e, strings.getString("main.error_caption"),
JOptionPane.ERROR_MESSAGE);
}
}
void startPlaying(Micropolis newEngine, File file)
{
MainWindow win = (MainWindow) getOwner();
win.setEngine(newEngine);
win.currentFile = file;
dispose();
}
private void onPlayClicked()
{
engine.setGameLevel(getSelectedGameLevel());
startPlaying(engine, null);
}
private void onCancelClicked()
{
dispose();
}
private void onQuitClicked()
{
System.exit(0);
}
private int getSelectedGameLevel()
{
for (int lev : levelBtns.keySet())
{
if (levelBtns.get(lev).isSelected()) {
return lev;
}
}
return GameLevel.MIN_LEVEL;
}
private void setGameLevel(int level)
{
for (int lev : levelBtns.keySet())
{
levelBtns.get(lev).setSelected(lev == level);
}
}
}

View file

@ -0,0 +1,176 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.gui;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import micropolisj.engine.*;
import static micropolisj.gui.ColorParser.parseColor;
public class NotificationPane extends JPanel
{
JLabel headerLbl;
JViewport mapViewport;
MicropolisDrawingArea mapView;
JPanel mainPane;
JComponent infoPane;
static final Dimension VIEWPORT_SIZE = new Dimension(160,160);
static final Color QUERY_COLOR = new Color(255,165,0);
static final ResourceBundle strings = MainWindow.strings;
static final ResourceBundle mstrings = ResourceBundle.getBundle("micropolisj.CityMessages");
static final ResourceBundle s_strings = ResourceBundle.getBundle("micropolisj.StatusMessages");
public NotificationPane(Micropolis engine)
{
super(new BorderLayout());
setVisible(false);
headerLbl = new JLabel();
headerLbl.setOpaque(true);
headerLbl.setHorizontalAlignment(SwingConstants.CENTER);
headerLbl.setBorder(BorderFactory.createRaisedBevelBorder());
add(headerLbl, BorderLayout.NORTH);
JButton dismissBtn = new JButton(strings.getString("notification.dismiss"));
dismissBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
onDismissClicked();
}});
add(dismissBtn, BorderLayout.SOUTH);
mainPane = new JPanel(new BorderLayout());
add(mainPane, BorderLayout.CENTER);
JPanel viewportContainer = new JPanel(new BorderLayout());
viewportContainer.setBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(8,4,8,4),
BorderFactory.createLineBorder(Color.BLACK)
));
mainPane.add(viewportContainer, BorderLayout.WEST);
mapViewport = new JViewport();
mapViewport.setPreferredSize(VIEWPORT_SIZE);
mapViewport.setMaximumSize(VIEWPORT_SIZE);
mapViewport.setMinimumSize(VIEWPORT_SIZE);
viewportContainer.add(mapViewport, BorderLayout.CENTER);
mapView = new MicropolisDrawingArea(engine);
mapViewport.setView(mapView);
}
private void onDismissClicked()
{
setVisible(false);
}
void setPicture(Micropolis engine, int xpos, int ypos)
{
Dimension sz = VIEWPORT_SIZE;
mapView.setEngine(engine);
Rectangle r = mapView.getTileBounds(xpos,ypos);
mapViewport.setViewPosition(new Point(
r.x + r.width/2 - sz.width/2,
r.y + r.height/2 - sz.height/2
));
}
public void showMessage(Micropolis engine, MicropolisMessage msg, int xpos, int ypos)
{
setPicture(engine, xpos, ypos);
if (infoPane != null) {
mainPane.remove(infoPane);
infoPane = null;
}
headerLbl.setText(mstrings.getString(msg.name()+".title"));
headerLbl.setBackground(parseColor(mstrings.getString(msg.name()+".color")));
JLabel myLabel = new JLabel("<html><p>"+
mstrings.getString(msg.name()+".detail") + "</p></html>");
myLabel.setPreferredSize(new Dimension(1,1));
infoPane = myLabel;
mainPane.add(myLabel, BorderLayout.CENTER);
setVisible(true);
}
public void showZoneStatus(Micropolis engine, int xpos, int ypos, ZoneStatus zone)
{
headerLbl.setText(strings.getString("notification.query_hdr"));
headerLbl.setBackground(QUERY_COLOR);
String buildingStr = s_strings.getString("zone."+zone.building);
String popDensityStr = s_strings.getString("status."+zone.popDensity);
String landValueStr = s_strings.getString("status."+zone.landValue);
String crimeLevelStr = s_strings.getString("status."+zone.crimeLevel);
String pollutionStr = s_strings.getString("status."+zone.pollution);
String growthRateStr = s_strings.getString("status."+zone.growthRate);
setPicture(engine, xpos, ypos);
if (infoPane != null) {
mainPane.remove(infoPane);
infoPane = null;
}
JPanel p = new JPanel(new GridBagLayout());
mainPane.add(p, BorderLayout.CENTER);
infoPane = p;
GridBagConstraints c1 = new GridBagConstraints();
GridBagConstraints c2 = new GridBagConstraints();
c1.gridx = 0;
c2.gridx = 1;
c1.gridy = c2.gridy = 0;
c1.anchor = GridBagConstraints.WEST;
c2.anchor = GridBagConstraints.WEST;
c1.insets = new Insets(0,0,0,8);
c2.weightx = 1.0;
p.add(new JLabel(strings.getString("notification.zone_lbl")), c1);
p.add(new JLabel(buildingStr), c2);
c1.gridy = ++c2.gridy;
p.add(new JLabel(strings.getString("notification.density_lbl")), c1);
p.add(new JLabel(popDensityStr), c2);
c1.gridy = ++c2.gridy;
p.add(new JLabel(strings.getString("notification.value_lbl")), c1);
p.add(new JLabel(landValueStr), c2);
c1.gridy = ++c2.gridy;
p.add(new JLabel(strings.getString("notification.crime_lbl")), c1);
p.add(new JLabel(crimeLevelStr), c2);
c1.gridy = ++c2.gridy;
p.add(new JLabel(strings.getString("notification.pollution_lbl")), c1);
p.add(new JLabel(pollutionStr), c2);
c1.gridy = ++c2.gridy;
p.add(new JLabel(strings.getString("notification.growth_lbl")), c1);
p.add(new JLabel(growthRateStr), c2);
c1.gridy++;
c1.gridwidth = 2;
c1.weighty = 1.0;
p.add(new JLabel(), c1);
setVisible(true);
}
}

View file

@ -0,0 +1,509 @@
// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.
package micropolisj.gui;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.net.URL;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import micropolisj.engine.*;
import static micropolisj.engine.TileConstants.*;
public class OverlayMapView extends JComponent
implements Scrollable, MapListener
{
Micropolis engine;
ArrayList<ConnectedView> views = new ArrayList<>();
MapState mapState = MapState.ALL;
public OverlayMapView(Micropolis _engine)
{
assert _engine != null;
MouseAdapter mouse = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent ev)
{
onMousePressed(ev);
}
@Override
public void mouseDragged(MouseEvent ev)
{
onMouseDragged(ev);
}
};
addMouseListener(mouse);
addMouseMotionListener(mouse);
setEngine(_engine);
}
public void setEngine(Micropolis newEngine)
{
assert newEngine != null;
if (engine != null) { //old engine
engine.removeMapListener(this);
}
engine = newEngine;
if (engine != null) { //new engine
engine.addMapListener(this);
}
invalidate(); //map size may have changed
repaint();
engine.calculateCenterMass();
dragViewToCityCenter();
}
public MapState getMapState()
{
return mapState;
}
@Override
public Dimension getPreferredSize()
{
return new Dimension(
getInsets().left + getInsets().right + TILE_WIDTH*engine.getWidth(),
getInsets().top + getInsets().bottom + TILE_HEIGHT*engine.getHeight()
);
}
public void setMapState(MapState newState)
{
if (mapState == newState)
return;
mapState = newState;
repaint();
}
static BufferedImage tileArrayImage = loadImage("/tilessm.png");
static final int TILE_WIDTH = 3;
static final int TILE_HEIGHT = 3;
static final int TILE_OFFSET_Y = 3;
static BufferedImage loadImage(String resourceName)
{
URL iconUrl = MicropolisDrawingArea.class.getResource(resourceName);
Image refImage = new ImageIcon(iconUrl).getImage();
BufferedImage bi = new BufferedImage(refImage.getWidth(null), refImage.getHeight(null),
BufferedImage.TYPE_INT_RGB);
Graphics2D gr = bi.createGraphics();
gr.drawImage(refImage, 0, 0, null);
return bi;
}
static final Color VAL_LOW = new Color(0xbfbfbf);
static final Color VAL_MEDIUM = new Color(0xffff00);
static final Color VAL_HIGH = new Color(0xff7f00);
static final Color VAL_VERYHIGH = new Color(0xff0000);
static final Color VAL_PLUS = new Color(0x007f00);
static final Color VAL_VERYPLUS = new Color(0x00e600);
static final Color VAL_MINUS = new Color(0xff7f00);
static final Color VAL_VERYMINUS = new Color(0xffff00);
private Color getCI(int x)
{
if (x < 50)
return null;
else if (x < 100)
return VAL_LOW;
else if (x < 150)
return VAL_MEDIUM;
else if (x < 200)
return VAL_HIGH;
else
return VAL_VERYHIGH;
}
private Color getCI_rog(int x)
{
if (x > 100)
return VAL_VERYPLUS;
else if (x > 20)
return VAL_PLUS;
else if (x < -100)
return VAL_VERYMINUS;
else if (x < -20)
return VAL_MINUS;
else
return null;
}
private void drawLandMap(Graphics gr)
{
int [][] A = engine.landValueMem;
for (int y = 0; y < A.length; y++) {
for (int x = 0; x < A[y].length; x++) {
maybeDrawRect(gr, getCI(A[y][x]),x*6,y*6,6,6);
}
}
}
private void drawPollutionMap(Graphics gr)
{
int [][] A = engine.pollutionMem;
for (int y = 0; y < A.length; y++) {
for (int x = 0; x < A[y].length; x++) {
maybeDrawRect(gr, getCI(10 + A[y][x]),x*6,y*6,6,6);
}
}
}
private void drawCrimeMap(Graphics gr)
{
int [][] A = engine.crimeMem;
for (int y = 0; y < A.length; y++) {
for (int x = 0; x < A[y].length; x++) {
maybeDrawRect(gr, getCI(A[y][x]),x*6,y*6,6,6);
}
}
}
private void drawTrafficMap(Graphics gr)
{
int [][] A = engine.trfDensity;
for (int y = 0; y < A.length; y++) {
for (int x = 0; x < A[y].length; x++) {
maybeDrawRect(gr, getCI(A[y][x]),x*6,y*6,6,6);
}
}
}
private void drawPopDensity(Graphics gr)
{
int [][] A = engine.popDensity;
for (int y = 0; y < A.length; y++) {
for (int x = 0; x < A[y].length; x++) {
maybeDrawRect(gr, getCI(A[y][x]),x*6,y*6,6,6);
}
}
}
private void drawRateOfGrowth(Graphics gr)
{
int [][] A = engine.rateOGMem;
for (int y = 0; y < A.length; y++) {
for (int x = 0; x < A[y].length; x++) {
maybeDrawRect(gr, getCI_rog(A[y][x]),x*24,y*24,24,24);
}
}
}
private void drawFireRadius(Graphics gr)
{
int [][] A = engine.fireRate;
for (int y = 0; y < A.length; y++) {
for (int x = 0; x < A[y].length; x++) {
maybeDrawRect(gr, getCI(A[y][x]),x*24,y*24,24,24);
}
}
}
private void drawPoliceRadius(Graphics gr)
{
int [][] A = engine.policeMapEffect;
for (int y = 0; y < A.length; y++) {
for (int x = 0; x < A[y].length; x++) {
maybeDrawRect(gr, getCI(A[y][x]),x*24,y*24,24,24);
}
}
}
private void maybeDrawRect(Graphics gr, Color col, int x, int y, int width, int height)
{
if (col != null) {
gr.setColor(col);
gr.fillRect(x,y,width,height);
}
}
static final int UNPOWERED = 0x6666e6; //lightblue
static final int POWERED = 0xff0000; //red
static final int CONDUCTIVE = 0xbfbfbf; //lightgray
private int checkPower(BufferedImage img, int x, int y, int rawTile)
{
int pix;
if ((rawTile & LOMASK) <= 63) {
return rawTile & LOMASK;
}
else if ((rawTile & ZONEBIT) != 0) {
// zone
pix = ((rawTile & PWRBIT) != 0) ? POWERED : UNPOWERED;
}
else if ((rawTile & CONDBIT) != 0) {
pix = CONDUCTIVE;
}
else {
return DIRT;
}
for (int yy = 0; yy < TILE_HEIGHT; yy++)
{
for (int xx = 0; xx < TILE_WIDTH; xx++)
{
img.setRGB(x*TILE_WIDTH+xx,y*TILE_HEIGHT+yy, pix);
}
}
return -1; //this special value tells caller to skip the tile bitblt,
//since it was performed here
}
@Override
public void paintComponent(Graphics gr)
{
final int width = engine.getWidth();
final int height = engine.getHeight();
BufferedImage img = new BufferedImage(width*TILE_WIDTH, height*TILE_HEIGHT,
BufferedImage.TYPE_INT_RGB);
final Insets INSETS = getInsets();
Rectangle clipRect = gr.getClipBounds();
int minX = Math.max(0, (clipRect.x - INSETS.left) / TILE_WIDTH);
int minY = Math.max(0, (clipRect.y - INSETS.top) / TILE_HEIGHT);
int maxX = Math.min(width, 1 + (clipRect.x - INSETS.left + clipRect.width-1) / TILE_WIDTH);
int maxY = Math.min(height, 1 + (clipRect.y - INSETS.top + clipRect.height-1) / TILE_HEIGHT);
for (int y = minY; y < maxY; y++)
{
for (int x = minX; x < maxX; x++)
{
int tile = engine.getTile(x,y) & LOMASK;
switch (mapState) {
case RESIDENTIAL:
if (tile >= COMBASE) { tile = DIRT; }
break;
case COMMERCIAL:
if (tile > COMLAST || (tile >= RESBASE && tile < COMBASE)) { tile = DIRT; }
break;
case INDUSTRIAL:
if ((tile >= RESBASE && tile < INDBASE) ||
(tile >= PORTBASE && tile < SMOKEBASE) ||
(tile >= TINYEXP && tile < 884) ||
tile >= FOOTBALLGAME1)
{ tile = DIRT; }
break;
case POWER_OVERLAY:
tile = checkPower(img, x, y, engine.getTile(x,y));
break;
case TRANSPORT:
case TRAFFIC_OVERLAY:
if (tile >= RESBASE ||
(tile >= 207 && tile <= LVPOWER10) ||
tile == 223) { tile = DIRT; }
break;
default:
}
if (tile != -1) {
for (int yy = 0; yy < TILE_HEIGHT; yy++)
{
for (int xx = 0; xx < TILE_WIDTH; xx++)
{
img.setRGB(x*TILE_WIDTH+xx,y*TILE_HEIGHT+yy,
tileArrayImage.getRGB(xx,tile*TILE_OFFSET_Y+yy));
}
}
}
}
}
gr.drawImage(img, INSETS.left, INSETS.top, null);
gr = gr.create();
gr.translate(INSETS.left, INSETS.top);
switch (mapState) {
case POLICE_OVERLAY:
drawPoliceRadius(gr); break;
case FIRE_OVERLAY:
drawFireRadius(gr); break;
case LANDVALUE_OVERLAY:
drawLandMap(gr); break;
case CRIME_OVERLAY:
drawCrimeMap(gr); break;
case POLLUTE_OVERLAY:
drawPollutionMap(gr); break;
case TRAFFIC_OVERLAY:
drawTrafficMap(gr); break;
case GROWTHRATE_OVERLAY:
drawRateOfGrowth(gr); break;
case POPDEN_OVERLAY:
drawPopDensity(gr); break;
default:
}
for (ConnectedView cv : views)
{
Rectangle rect = getViewRect(cv);
gr.setColor(Color.WHITE);
gr.drawRect(rect.x-2,rect.y-2,rect.width+2,rect.height+2);
gr.setColor(Color.BLACK);
gr.drawRect(rect.x-0,rect.y-0,rect.width+2,rect.height+2);
gr.setColor(Color.YELLOW);
gr.drawRect(rect.x-1,rect.y-1,rect.width+2,rect.height+2);
}
}
Rectangle getViewRect(ConnectedView cv)
{
Rectangle rawRect = cv.scrollPane.getViewport().getViewRect();
return new Rectangle(
rawRect.x * 3 / 16,
rawRect.y * 3 / 16,
rawRect.width * 3 / 16,
rawRect.height * 3 / 16
);
}
private void dragViewTo(Point p)
{
if (views.isEmpty())
return;
ConnectedView cv = views.get(0);
Dimension d = cv.scrollPane.getViewport().getExtentSize();
Dimension mapSize = cv.scrollPane.getViewport().getViewSize();
Point np = new Point(
p.x * 16 / 3 - d.width / 2,
p.y * 16 / 3 - d.height / 2
);
np.x = Math.max(0, Math.min(np.x, mapSize.width - d.width));
np.y = Math.max(0, Math.min(np.y, mapSize.height - d.height));
cv.scrollPane.getViewport().setViewPosition(np);
}
//implements Scrollable
public Dimension getPreferredScrollableViewportSize()
{
return new Dimension(120,120);
}
//implements Scrollable
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)
{
if (orientation == SwingConstants.VERTICAL)
return visibleRect.height;
else
return visibleRect.width;
}
//implements Scrollable
public boolean getScrollableTracksViewportWidth()
{
return false;
}
//implements Scrollable
public boolean getScrollableTracksViewportHeight()
{
return false;
}
//implements Scrollable
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)
{
if (orientation == SwingConstants.VERTICAL)
return TILE_HEIGHT;
else
return TILE_WIDTH;
}
//implements MapListener
public void mapOverlayDataChanged(MapState overlayDataType)
{
repaint();
}
//implements MapListener
public void spriteMoved(Sprite sprite)
{
}
//implements MapListener
public void tileChanged(int xpos, int ypos)
{
Rectangle r = new Rectangle(xpos*TILE_WIDTH, ypos * TILE_HEIGHT, TILE_WIDTH, TILE_HEIGHT);
repaint(r);
}
//implements MapListener
public void wholeMapChanged()
{
repaint();
engine.calculateCenterMass();
dragViewToCityCenter();
}
public void dragViewToCityCenter()
{
dragViewTo(new Point(TILE_WIDTH * engine.centerMassX + 1,
TILE_HEIGHT * engine.centerMassY + 1));
}
class ConnectedView implements ChangeListener
{
JScrollPane scrollPane;
ConnectedView(MicropolisDrawingArea view, JScrollPane scrollPane)
{
this.scrollPane = scrollPane;
scrollPane.getViewport().addChangeListener(this);
}
public void stateChanged(ChangeEvent ev)
{
repaint();
}
}
public void connectView(MicropolisDrawingArea view, JScrollPane scrollPane)
{
ConnectedView cv = new ConnectedView(view, scrollPane);
views.add(cv);
repaint();
}
private void onMousePressed(MouseEvent ev)
{
if (ev.getButton() == MouseEvent.BUTTON1)
dragViewTo(ev.getPoint());
}
private void onMouseDragged(MouseEvent ev)
{
if ((ev.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0)
return;
dragViewTo(ev.getPoint());
}
}

View file

@ -0,0 +1,9 @@
<html><head></head>
<body>
<p>Contains the front-end user interface that drives the game.</p>
<p>
Most of the funtionality is tied in by the MainWindow class.
The MicropolisDrawingArea class provides the city renderer.
The OverlapMapView class provides the mini-map.
</p>
</body></html>