powergrid-engine 1.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/index.js +17 -0
  5. package/dist/src/available-moves.d.ts +24 -0
  6. package/dist/src/available-moves.js +363 -0
  7. package/dist/src/engine.d.ts +20 -0
  8. package/dist/src/engine.js +1937 -0
  9. package/dist/src/gamestate.d.ts +135 -0
  10. package/dist/src/gamestate.js +30 -0
  11. package/dist/src/log.d.ts +14 -0
  12. package/dist/src/log.js +2 -0
  13. package/dist/src/maps/america.d.ts +55 -0
  14. package/dist/src/maps/america.js +411 -0
  15. package/dist/src/maps/australia.d.ts +46 -0
  16. package/dist/src/maps/australia.js +138 -0
  17. package/dist/src/maps/badenwurttemberg.d.ts +46 -0
  18. package/dist/src/maps/badenwurttemberg.js +163 -0
  19. package/dist/src/maps/benelux.d.ts +46 -0
  20. package/dist/src/maps/benelux.js +210 -0
  21. package/dist/src/maps/brazil.d.ts +54 -0
  22. package/dist/src/maps/brazil.js +292 -0
  23. package/dist/src/maps/centraleurope.d.ts +54 -0
  24. package/dist/src/maps/centraleurope.js +236 -0
  25. package/dist/src/maps/china.d.ts +54 -0
  26. package/dist/src/maps/china.js +262 -0
  27. package/dist/src/maps/france.d.ts +54 -0
  28. package/dist/src/maps/france.js +290 -0
  29. package/dist/src/maps/germany.d.ts +57 -0
  30. package/dist/src/maps/germany.js +328 -0
  31. package/dist/src/maps/indian.d.ts +54 -0
  32. package/dist/src/maps/indian.js +283 -0
  33. package/dist/src/maps/italy.d.ts +54 -0
  34. package/dist/src/maps/italy.js +190 -0
  35. package/dist/src/maps/japan.d.ts +46 -0
  36. package/dist/src/maps/japan.js +144 -0
  37. package/dist/src/maps/korea.d.ts +54 -0
  38. package/dist/src/maps/korea.js +186 -0
  39. package/dist/src/maps/middleeast.d.ts +54 -0
  40. package/dist/src/maps/middleeast.js +225 -0
  41. package/dist/src/maps/northerneurope.d.ts +54 -0
  42. package/dist/src/maps/northerneurope.js +197 -0
  43. package/dist/src/maps/quebec.d.ts +54 -0
  44. package/dist/src/maps/quebec.js +283 -0
  45. package/dist/src/maps/russia.d.ts +54 -0
  46. package/dist/src/maps/russia.js +286 -0
  47. package/dist/src/maps/southafrica.d.ts +46 -0
  48. package/dist/src/maps/southafrica.js +152 -0
  49. package/dist/src/maps/spainportugal.d.ts +54 -0
  50. package/dist/src/maps/spainportugal.js +289 -0
  51. package/dist/src/maps/ukireland.d.ts +52 -0
  52. package/dist/src/maps/ukireland.js +176 -0
  53. package/dist/src/maps.d.ts +50 -0
  54. package/dist/src/maps.js +61 -0
  55. package/dist/src/move.d.ts +63 -0
  56. package/dist/src/move.js +15 -0
  57. package/dist/src/powerPlants.d.ts +4 -0
  58. package/dist/src/powerPlants.js +60 -0
  59. package/dist/src/prices.d.ts +7 -0
  60. package/dist/src/prices.js +10 -0
  61. package/dist/src/randomizeMap.d.ts +3 -0
  62. package/dist/src/randomizeMap.js +244 -0
  63. package/dist/src/utils.d.ts +2 -0
  64. package/dist/src/utils.js +24 -0
  65. package/dist/wrapper.d.ts +30 -0
  66. package/dist/wrapper.js +127 -0
  67. package/index.ts +6 -0
  68. package/package.json +51 -0
  69. package/src/available-moves.ts +450 -0
  70. package/src/engine.spec.ts +163 -0
  71. package/src/engine.ts +2270 -0
  72. package/src/fixtures/GermanyRecharged.json +6627 -0
  73. package/src/fixtures/USAOriginal.json +5216 -0
  74. package/src/fixtures/supply.json +5792 -0
  75. package/src/fixtures/undo.json +4102 -0
  76. package/src/gamestate.ts +164 -0
  77. package/src/log.ts +17 -0
  78. package/src/maps/america.ts +411 -0
  79. package/src/maps/australia.ts +137 -0
  80. package/src/maps/badenwurttemberg.ts +162 -0
  81. package/src/maps/benelux.ts +210 -0
  82. package/src/maps/brazil.ts +306 -0
  83. package/src/maps/centraleurope.ts +235 -0
  84. package/src/maps/china.ts +268 -0
  85. package/src/maps/france.ts +295 -0
  86. package/src/maps/germany.ts +328 -0
  87. package/src/maps/indian.ts +289 -0
  88. package/src/maps/italy.ts +189 -0
  89. package/src/maps/japan.ts +143 -0
  90. package/src/maps/korea.ts +185 -0
  91. package/src/maps/middleeast.ts +225 -0
  92. package/src/maps/northerneurope.ts +196 -0
  93. package/src/maps/quebec.ts +304 -0
  94. package/src/maps/russia.ts +292 -0
  95. package/src/maps/southafrica.ts +151 -0
  96. package/src/maps/spainportugal.ts +295 -0
  97. package/src/maps/ukireland.ts +175 -0
  98. package/src/maps.ts +123 -0
  99. package/src/move.ts +83 -0
  100. package/src/powerPlants.ts +59 -0
  101. package/src/prices.ts +10 -0
  102. package/src/randomizeMap.ts +288 -0
  103. package/src/rankings.spec.ts +18 -0
  104. package/src/utils.spec.ts +13 -0
  105. package/src/utils.ts +23 -0
  106. package/tsconfig.json +17 -0
  107. package/wrapper.ts +126 -0
@@ -0,0 +1,63 @@
1
+ import { ResourceType } from './gamestate';
2
+ export declare namespace Moves {
3
+ interface MoveChoosePowerPlant {
4
+ name: MoveName.ChoosePowerPlant;
5
+ data: number;
6
+ usedPlantDiscount?: boolean;
7
+ }
8
+ interface MoveBid {
9
+ name: MoveName.Bid;
10
+ data: number;
11
+ }
12
+ interface MoveDiscardPowerPlant {
13
+ name: MoveName.DiscardPowerPlant;
14
+ data: number;
15
+ extra?: number[];
16
+ }
17
+ interface MoveDiscardResources {
18
+ name: MoveName.DiscardResources;
19
+ data: ResourceType;
20
+ }
21
+ interface MoveBuyResource {
22
+ name: MoveName.BuyResource;
23
+ data: {
24
+ resource: ResourceType;
25
+ };
26
+ fromSupply?: boolean;
27
+ }
28
+ interface MoveBuild {
29
+ name: MoveName.Build;
30
+ data: {
31
+ name: string;
32
+ price: number;
33
+ };
34
+ }
35
+ interface MoveUsePowerPlant {
36
+ name: MoveName.UsePowerPlant;
37
+ data: {
38
+ powerPlant: number;
39
+ resourcesSpent: ResourceType[];
40
+ citiesPowered: number;
41
+ };
42
+ }
43
+ interface MovePass {
44
+ name: MoveName.Pass;
45
+ data: true;
46
+ }
47
+ interface MoveUndo {
48
+ name: MoveName.Undo;
49
+ data: boolean;
50
+ }
51
+ }
52
+ export declare type Move = Moves.MoveChoosePowerPlant | Moves.MoveBid | Moves.MoveDiscardPowerPlant | Moves.MoveDiscardResources | Moves.MoveBuyResource | Moves.MoveBuild | Moves.MoveUsePowerPlant | Moves.MovePass | Moves.MoveUndo;
53
+ export declare enum MoveName {
54
+ ChoosePowerPlant = "ChoosePowerPlant",
55
+ Bid = "Bid",
56
+ DiscardPowerPlant = "DiscardPowerPlant",
57
+ DiscardResources = "DiscardResources",
58
+ BuyResource = "BuyResource",
59
+ Build = "Build",
60
+ UsePowerPlant = "UsePowerPlant",
61
+ Pass = "Pass",
62
+ Undo = "Undo"
63
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MoveName = void 0;
4
+ var MoveName;
5
+ (function (MoveName) {
6
+ MoveName["ChoosePowerPlant"] = "ChoosePowerPlant";
7
+ MoveName["Bid"] = "Bid";
8
+ MoveName["DiscardPowerPlant"] = "DiscardPowerPlant";
9
+ MoveName["DiscardResources"] = "DiscardResources";
10
+ MoveName["BuyResource"] = "BuyResource";
11
+ MoveName["Build"] = "Build";
12
+ MoveName["UsePowerPlant"] = "UsePowerPlant";
13
+ MoveName["Pass"] = "Pass";
14
+ MoveName["Undo"] = "Undo";
15
+ })(MoveName = exports.MoveName || (exports.MoveName = {}));
@@ -0,0 +1,4 @@
1
+ import { PowerPlant } from './gamestate';
2
+ declare const powerPlants: PowerPlant[];
3
+ declare const indiaPowerPlants: PowerPlant[];
4
+ export { powerPlants, indiaPowerPlants };
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.indiaPowerPlants = exports.powerPlants = void 0;
4
+ const lodash_1 = require("lodash");
5
+ const gamestate_1 = require("./gamestate");
6
+ const powerPlants = [
7
+ { number: 3, type: gamestate_1.PowerPlantType.Oil, cost: 2, citiesPowered: 1 },
8
+ { number: 4, type: gamestate_1.PowerPlantType.Coal, cost: 2, citiesPowered: 1 },
9
+ { number: 5, type: gamestate_1.PowerPlantType.Hybrid, cost: 2, citiesPowered: 1 },
10
+ { number: 6, type: gamestate_1.PowerPlantType.Garbage, cost: 1, citiesPowered: 1 },
11
+ { number: 7, type: gamestate_1.PowerPlantType.Oil, cost: 3, citiesPowered: 2 },
12
+ { number: 8, type: gamestate_1.PowerPlantType.Coal, cost: 3, citiesPowered: 2 },
13
+ { number: 9, type: gamestate_1.PowerPlantType.Oil, cost: 1, citiesPowered: 1 },
14
+ { number: 10, type: gamestate_1.PowerPlantType.Coal, cost: 2, citiesPowered: 2 },
15
+ { number: 11, type: gamestate_1.PowerPlantType.Uranium, cost: 1, citiesPowered: 2 },
16
+ { number: 12, type: gamestate_1.PowerPlantType.Hybrid, cost: 2, citiesPowered: 2 },
17
+ { number: 13, type: gamestate_1.PowerPlantType.Wind, cost: 0, citiesPowered: 1 },
18
+ { number: 14, type: gamestate_1.PowerPlantType.Garbage, cost: 2, citiesPowered: 2 },
19
+ { number: 15, type: gamestate_1.PowerPlantType.Coal, cost: 2, citiesPowered: 3 },
20
+ { number: 16, type: gamestate_1.PowerPlantType.Oil, cost: 2, citiesPowered: 3 },
21
+ { number: 17, type: gamestate_1.PowerPlantType.Uranium, cost: 1, citiesPowered: 2 },
22
+ { number: 18, type: gamestate_1.PowerPlantType.Wind, cost: 0, citiesPowered: 2 },
23
+ { number: 19, type: gamestate_1.PowerPlantType.Garbage, cost: 2, citiesPowered: 3 },
24
+ { number: 20, type: gamestate_1.PowerPlantType.Coal, cost: 3, citiesPowered: 5 },
25
+ { number: 21, type: gamestate_1.PowerPlantType.Hybrid, cost: 2, citiesPowered: 4 },
26
+ { number: 22, type: gamestate_1.PowerPlantType.Wind, cost: 0, citiesPowered: 2 },
27
+ { number: 23, type: gamestate_1.PowerPlantType.Uranium, cost: 1, citiesPowered: 3 },
28
+ { number: 24, type: gamestate_1.PowerPlantType.Garbage, cost: 2, citiesPowered: 4 },
29
+ { number: 25, type: gamestate_1.PowerPlantType.Coal, cost: 2, citiesPowered: 5 },
30
+ { number: 26, type: gamestate_1.PowerPlantType.Oil, cost: 2, citiesPowered: 5 },
31
+ { number: 27, type: gamestate_1.PowerPlantType.Wind, cost: 0, citiesPowered: 3 },
32
+ { number: 28, type: gamestate_1.PowerPlantType.Uranium, cost: 1, citiesPowered: 4 },
33
+ { number: 29, type: gamestate_1.PowerPlantType.Hybrid, cost: 1, citiesPowered: 4 },
34
+ { number: 30, type: gamestate_1.PowerPlantType.Garbage, cost: 3, citiesPowered: 6 },
35
+ { number: 31, type: gamestate_1.PowerPlantType.Coal, cost: 3, citiesPowered: 6 },
36
+ { number: 32, type: gamestate_1.PowerPlantType.Oil, cost: 3, citiesPowered: 6 },
37
+ { number: 33, type: gamestate_1.PowerPlantType.Wind, cost: 0, citiesPowered: 4 },
38
+ { number: 34, type: gamestate_1.PowerPlantType.Uranium, cost: 1, citiesPowered: 5 },
39
+ { number: 35, type: gamestate_1.PowerPlantType.Oil, cost: 1, citiesPowered: 5 },
40
+ { number: 36, type: gamestate_1.PowerPlantType.Coal, cost: 3, citiesPowered: 7 },
41
+ { number: 37, type: gamestate_1.PowerPlantType.Wind, cost: 0, citiesPowered: 4 },
42
+ { number: 38, type: gamestate_1.PowerPlantType.Garbage, cost: 3, citiesPowered: 7 },
43
+ { number: 39, type: gamestate_1.PowerPlantType.Uranium, cost: 1, citiesPowered: 6 },
44
+ { number: 40, type: gamestate_1.PowerPlantType.Oil, cost: 2, citiesPowered: 6 },
45
+ { number: 42, type: gamestate_1.PowerPlantType.Coal, cost: 2, citiesPowered: 6 },
46
+ { number: 44, type: gamestate_1.PowerPlantType.Wind, cost: 0, citiesPowered: 5 },
47
+ { number: 46, type: gamestate_1.PowerPlantType.Hybrid, cost: 3, citiesPowered: 7 },
48
+ { number: 50, type: gamestate_1.PowerPlantType.Nuclear, cost: 0, citiesPowered: 6 },
49
+ { number: 99, type: gamestate_1.PowerPlantType.Step3, cost: 0, citiesPowered: 6 },
50
+ ];
51
+ exports.powerPlants = powerPlants;
52
+ const indiaPowerPlants = lodash_1.cloneDeep(powerPlants).filter((pp) => pp.number != 11);
53
+ exports.indiaPowerPlants = indiaPowerPlants;
54
+ // Garbage plants cost one more garbage to run, but have no additional storage.
55
+ indiaPowerPlants.forEach((pp) => {
56
+ if (pp.type == gamestate_1.PowerPlantType.Garbage) {
57
+ pp.storage = 2 * pp.cost;
58
+ pp.cost++;
59
+ }
60
+ });
@@ -0,0 +1,7 @@
1
+ declare const prices: {
2
+ coal: number[];
3
+ oil: number[];
4
+ garbage: number[];
5
+ uranium: number[];
6
+ };
7
+ export default prices;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const gamestate_1 = require("./gamestate");
4
+ const prices = {
5
+ [gamestate_1.ResourceType.Coal]: [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8],
6
+ [gamestate_1.ResourceType.Oil]: [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8],
7
+ [gamestate_1.ResourceType.Garbage]: [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8],
8
+ [gamestate_1.ResourceType.Uranium]: [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16],
9
+ };
10
+ exports.default = prices;
@@ -0,0 +1,3 @@
1
+ import seedrandom from 'seedrandom';
2
+ import { GameMap } from './maps';
3
+ export declare function createRandomizedMap(map: GameMap, regionCount: number, rng: seedrandom.prng): GameMap;
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createRandomizedMap = void 0;
7
+ const delaunator_1 = __importDefault(require("delaunator"));
8
+ const lodash_1 = require("lodash");
9
+ function createRandomizedMap(map, regionCount, rng) {
10
+ const regions = [...new Set(map.cities.map((c) => c.region))];
11
+ const cities = Array(7 * regionCount)
12
+ .fill(0)
13
+ .map((_) => ({ name: '', region: '', x: 0, y: 0 }));
14
+ const widthByRegions = [420, 490, 490];
15
+ const heightByRegions = [490, 560, 700];
16
+ const width = widthByRegions[regionCount - 3];
17
+ const height = heightByRegions[regionCount - 3];
18
+ const gridCellSize = 70;
19
+ const gridWidth = Math.ceil(width / gridCellSize);
20
+ const gridHeight = Math.ceil(height / gridCellSize);
21
+ const gridLength = gridWidth * gridHeight;
22
+ const grid = Array(gridLength).fill(-1);
23
+ map.mapPosition = [100, 200];
24
+ map.layout = 'Portrait';
25
+ let emptyGrids = Array(gridLength)
26
+ .fill(0)
27
+ .map((_, i) => i);
28
+ cities.forEach((city, i) => {
29
+ const emptyGrid = emptyGrids[Math.floor(rng() * emptyGrids.length)];
30
+ const emptyGridY = Math.floor(emptyGrid / gridWidth);
31
+ const emptyGridX = emptyGrid % gridWidth;
32
+ city.x = Math.floor(emptyGridX * 2 * gridCellSize);
33
+ city.y = Math.floor(emptyGridY * gridCellSize);
34
+ if (emptyGridY % 2 === 0) {
35
+ city.x += gridCellSize;
36
+ }
37
+ const t = rng() * 2 * Math.PI;
38
+ const r = (rng() * gridCellSize) / 2;
39
+ city.x += r * Math.cos(t);
40
+ city.y += r * Math.sin(t);
41
+ emptyGrids = emptyGrids.filter((g) => g !== emptyGrid);
42
+ grid[emptyGrid] = i;
43
+ });
44
+ const cityDists = Array(cities.length)
45
+ .fill(0)
46
+ .map((_) => Array(cities.length).fill(0));
47
+ const cityConnections = Array(cities.length)
48
+ .fill(0)
49
+ .map((_) => Array(cities.length).fill(null));
50
+ const d = delaunator_1.default.from(cities, (c) => c.x, (c) => c.y);
51
+ for (let i = 0; i < d.triangles.length; i += 3) {
52
+ const city1 = cities[d.triangles[i]];
53
+ const city2 = cities[d.triangles[i + 1]];
54
+ const city3 = cities[d.triangles[i + 2]];
55
+ let dist = cityDists[d.triangles[i]][d.triangles[i + 1]];
56
+ const dist12Squared = dist
57
+ ? Math.pow(dist, 2)
58
+ : Math.pow(city1.x - city2.x, 2) + Math.pow(city1.y - city2.y, 2);
59
+ const dist12 = dist ||
60
+ (cityDists[d.triangles[i + 1]][d.triangles[i]] = cityDists[d.triangles[i]][d.triangles[i + 1]] =
61
+ Math.sqrt(dist12Squared));
62
+ dist = cityDists[d.triangles[i]][d.triangles[i + 2]];
63
+ const dist13Squared = dist
64
+ ? Math.pow(dist, 2)
65
+ : Math.pow(city1.x - city3.x, 2) + Math.pow(city1.y - city3.y, 2);
66
+ const dist13 = dist ||
67
+ (cityDists[d.triangles[i + 2]][d.triangles[i]] = cityDists[d.triangles[i]][d.triangles[i + 2]] =
68
+ Math.sqrt(dist13Squared));
69
+ dist = cityDists[d.triangles[i + 1]][d.triangles[i + 2]];
70
+ const dist23Squared = dist
71
+ ? Math.pow(dist, 2)
72
+ : Math.pow(city2.x - city3.x, 2) + Math.pow(city2.y - city3.y, 2);
73
+ const dist23 = dist ||
74
+ (cityDists[d.triangles[i + 2]][d.triangles[i + 1]] = cityDists[d.triangles[i + 1]][d.triangles[i + 2]] =
75
+ Math.sqrt(dist23Squared));
76
+ // arccos((P122 + P132 - P232) / (2 * P12 * P13))
77
+ const angle1 = Math.acos((dist12Squared + dist13Squared - dist23Squared) / (2 * dist12 * dist13));
78
+ const angle2 = Math.acos((dist12Squared + dist23Squared - dist13Squared) / (2 * dist12 * dist23));
79
+ const angle3 = Math.PI - angle1 - angle2;
80
+ const maxAngle = (105 * Math.PI) / 180;
81
+ if (cityConnections[d.triangles[i]][d.triangles[i + 1]] !== false && angle3 < maxAngle) {
82
+ cityConnections[d.triangles[i]][d.triangles[i + 1]] = true;
83
+ cityConnections[d.triangles[i + 1]][d.triangles[i]] = true;
84
+ }
85
+ else {
86
+ cityConnections[d.triangles[i]][d.triangles[i + 1]] = false;
87
+ cityConnections[d.triangles[i + 1]][d.triangles[i]] = false;
88
+ }
89
+ if (cityConnections[d.triangles[i]][d.triangles[i + 2]] !== false && angle2 < maxAngle) {
90
+ cityConnections[d.triangles[i]][d.triangles[i + 2]] = true;
91
+ cityConnections[d.triangles[i + 2]][d.triangles[i]] = true;
92
+ }
93
+ else {
94
+ cityConnections[d.triangles[i]][d.triangles[i + 2]] = false;
95
+ cityConnections[d.triangles[i + 2]][d.triangles[i]] = false;
96
+ }
97
+ if (cityConnections[d.triangles[i + 1]][d.triangles[i + 2]] !== false && angle1 < maxAngle) {
98
+ cityConnections[d.triangles[i + 1]][d.triangles[i + 2]] = true;
99
+ cityConnections[d.triangles[i + 2]][d.triangles[i + 1]] = true;
100
+ }
101
+ else {
102
+ cityConnections[d.triangles[i + 1]][d.triangles[i + 2]] = false;
103
+ cityConnections[d.triangles[i + 2]][d.triangles[i + 1]] = false;
104
+ }
105
+ }
106
+ let maxDist = 0;
107
+ let minDist = 1000;
108
+ for (let i = 0; i < cities.length; i++) {
109
+ for (let j = 0; j < i; j++) {
110
+ if (cityConnections[i][j]) {
111
+ const dist = cityDists[i][j];
112
+ maxDist = Math.max(maxDist, dist);
113
+ minDist = Math.min(minDist, dist);
114
+ }
115
+ }
116
+ }
117
+ const centroids = regionCount === 5
118
+ ? [
119
+ [width / 4, height / 4],
120
+ [width / 4, (3 * height) / 4],
121
+ [width / 2, height / 2],
122
+ [(3 * width) / 4, height / 4],
123
+ [(3 * width) / 4, (3 * height) / 4],
124
+ ]
125
+ : regionCount === 4
126
+ ? [
127
+ [width / 4, height / 4],
128
+ [width / 4, (3 * height) / 4],
129
+ [(3 * width) / 4, height / 4],
130
+ [(3 * width) / 4, (3 * height) / 4],
131
+ ]
132
+ : [
133
+ [width / 4, height / 4],
134
+ [width / 2, height / 2],
135
+ [(3 * width) / 4, (3 * height) / 4],
136
+ ];
137
+ const indexes = bkmeans(cities.map((c) => [c.x, c.y]), regionCount, centroids, cityConnections);
138
+ const countRegions = Array(regionCount).fill(0);
139
+ cities.forEach((city, index) => {
140
+ city.region = regions[indexes[index]];
141
+ city.name = lodash_1.upperFirst(city.region) + ' ' + (countRegions[indexes[index]]++ + 1);
142
+ });
143
+ const connections = [];
144
+ for (let i = 0; i < cities.length; i++) {
145
+ for (let j = 0; j < i; j++) {
146
+ if (cityConnections[i][j]) {
147
+ const city1 = cities[i];
148
+ const city2 = cities[j];
149
+ const dist = cityDists[i][j];
150
+ connections.push({
151
+ nodes: [city1.name, city2.name],
152
+ cost: Math.floor((5 * dist) / gridCellSize),
153
+ });
154
+ }
155
+ }
156
+ }
157
+ map.cities = cities;
158
+ map.connections = connections;
159
+ return map;
160
+ }
161
+ exports.createRandomizedMap = createRandomizedMap;
162
+ function bkmeans(points, regions, centroids, connections, iterations = 100) {
163
+ const indexes = Array(points.length).fill(0);
164
+ const pointsPerRegion = Math.ceil(points.length / regions);
165
+ let conv = false;
166
+ do {
167
+ // Assign
168
+ const regionsCount = Array(regions).fill(0);
169
+ const pointCentDists = Array(points.length)
170
+ .fill(0)
171
+ .map((_) => Array(regions).fill(0));
172
+ points.forEach((point, pi) => {
173
+ let minDist = null;
174
+ let index = 0;
175
+ centroids.forEach((centroid, ci) => {
176
+ const d = dist(point, centroid);
177
+ if (minDist === null || d < minDist) {
178
+ if (regionsCount[ci] < pointsPerRegion) {
179
+ minDist = d;
180
+ index = ci;
181
+ }
182
+ }
183
+ pointCentDists[pi][ci] = d;
184
+ });
185
+ indexes[pi] = index;
186
+ regionsCount[index]++;
187
+ });
188
+ // Update
189
+ const newCentroids = Array(centroids.length)
190
+ .fill(0)
191
+ .map((_) => [0, 0]);
192
+ const centroidsCount = Array(centroids.length).fill(0);
193
+ points.forEach((point, pi) => {
194
+ newCentroids[indexes[pi]][0] += point[0];
195
+ newCentroids[indexes[pi]][1] += point[1];
196
+ centroidsCount[indexes[pi]]++;
197
+ });
198
+ conv = true;
199
+ centroids.forEach((centroid, ci) => {
200
+ newCentroids[ci][0] = newCentroids[ci][0] / centroidsCount[ci];
201
+ newCentroids[ci][1] = newCentroids[ci][1] / centroidsCount[ci];
202
+ if (centroid[0] !== newCentroids[ci][0] || centroid[1] !== newCentroids[ci][1]) {
203
+ conv = false;
204
+ }
205
+ });
206
+ centroids = newCentroids;
207
+ console.log('iterations', iterations);
208
+ console.log('centroids', centroids);
209
+ conv = conv || --iterations === 0;
210
+ } while (!conv);
211
+ // Refine
212
+ let maxRefine = 10;
213
+ do {
214
+ console.log('Refining');
215
+ conv = true;
216
+ points.forEach((_, p1) => {
217
+ const index = indexes[p1];
218
+ const hasSameColorNeighbor = connections[p1].some((connection, p2) => connection && index === indexes[p2]);
219
+ if (!hasSameColorNeighbor) {
220
+ const centroid = centroids[index];
221
+ let minDist = null;
222
+ let closestNeighbor = null;
223
+ connections[p1].forEach((connection, p2) => {
224
+ if (connection) {
225
+ const neighbor = points[p2];
226
+ const d = dist(neighbor, centroid);
227
+ if (minDist === null || d < minDist) {
228
+ minDist = d;
229
+ closestNeighbor = p2;
230
+ }
231
+ }
232
+ });
233
+ indexes[p1] = indexes[closestNeighbor];
234
+ indexes[closestNeighbor] = index;
235
+ conv = false;
236
+ }
237
+ });
238
+ conv = conv || --maxRefine === 0;
239
+ } while (!conv);
240
+ return indexes;
241
+ }
242
+ function dist(p1, p2) {
243
+ return (p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]);
244
+ }
@@ -0,0 +1,2 @@
1
+ export declare function asserts<T>(move: any): asserts move is T;
2
+ export declare function shuffle<T>(array: T[], seed: string): T[];
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.shuffle = exports.asserts = void 0;
7
+ const seedrandom_1 = __importDefault(require("seedrandom"));
8
+ /* eslint-disable */
9
+ function asserts(move) { }
10
+ exports.asserts = asserts;
11
+ /* eslint-enable */
12
+ function shuffle(array, seed) {
13
+ const rng = seedrandom_1.default.alea(seed);
14
+ const reverse = new Map();
15
+ array.forEach((item, i) => {
16
+ let n = rng.int32();
17
+ while (reverse.has(n)) {
18
+ n = rng.int32();
19
+ }
20
+ reverse.set(n, i);
21
+ });
22
+ return [...reverse.keys()].sort().map((n) => array[reverse.get(n)]);
23
+ }
24
+ exports.shuffle = shuffle;
@@ -0,0 +1,30 @@
1
+ import type { GameState } from './index';
2
+ import { GameOptions } from './src/gamestate';
3
+ import { Move } from './src/move';
4
+ export declare function init(nbPlayers: number, expansions: string[], options: GameOptions, seed?: string): Promise<GameState>;
5
+ export declare function setPlayerMetaData(G: GameState, player: number, metaData: {
6
+ name: string;
7
+ }): GameState;
8
+ export declare function move(G: GameState, move: Move, player: number): Promise<GameState>;
9
+ export { ended, scores, stripSecret } from './src/engine';
10
+ export declare function rankings(G: GameState): number[];
11
+ export declare function factions(G: GameState): string[];
12
+ export declare function replay(G: GameState, { to }: {
13
+ to: number;
14
+ }): GameState;
15
+ export declare function round(G: GameState): number;
16
+ export declare function dropPlayer(G: GameState, playerNum: number): Promise<GameState>;
17
+ export declare function currentPlayer(G: GameState): number[];
18
+ export declare function messages(G: GameState): {
19
+ messages: never[];
20
+ data: GameState;
21
+ };
22
+ export declare function logLength(G: GameState, _player?: number): number;
23
+ export declare function logSlice(G: GameState, options?: {
24
+ player?: number;
25
+ start?: number;
26
+ end?: number;
27
+ }): {
28
+ log: import("./index").LogItem[];
29
+ availableMoves: (import("./index").AvailableMoves | null)[];
30
+ };
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.logSlice = exports.logLength = exports.messages = exports.currentPlayer = exports.dropPlayer = exports.round = exports.replay = exports.factions = exports.rankings = exports.stripSecret = exports.scores = exports.ended = exports.move = exports.setPlayerMetaData = exports.init = void 0;
23
+ const engine = __importStar(require("./src/engine"));
24
+ const engine_1 = require("./src/engine");
25
+ const move_1 = require("./src/move");
26
+ const utils_1 = require("./src/utils");
27
+ async function init(nbPlayers, expansions, options, seed) {
28
+ return engine.setup(nbPlayers, options, seed);
29
+ }
30
+ exports.init = init;
31
+ function setPlayerMetaData(G, player, metaData) {
32
+ G.players[player].name = metaData.name;
33
+ return G;
34
+ }
35
+ exports.setPlayerMetaData = setPlayerMetaData;
36
+ async function move(G, move, player) {
37
+ G = engine.move(G, move, player);
38
+ return G;
39
+ }
40
+ exports.move = move;
41
+ var engine_2 = require("./src/engine");
42
+ Object.defineProperty(exports, "ended", { enumerable: true, get: function () { return engine_2.ended; } });
43
+ Object.defineProperty(exports, "scores", { enumerable: true, get: function () { return engine_2.scores; } });
44
+ Object.defineProperty(exports, "stripSecret", { enumerable: true, get: function () { return engine_2.stripSecret; } });
45
+ function rankings(G) {
46
+ const sortedPlayers = engine_1.playersSortedByScore(G);
47
+ sortedPlayers.forEach((player, index) => {
48
+ player.ranking = index + 1;
49
+ if (index > 0) {
50
+ const prev = sortedPlayers[index - 1];
51
+ if (player.citiesPowered === prev.citiesPowered &&
52
+ player.money === prev.money &&
53
+ player.cities.length === prev.cities.length) {
54
+ player.ranking = prev.ranking;
55
+ }
56
+ }
57
+ });
58
+ return G.players.map((pl) => sortedPlayers.find((spl) => pl.id === spl.id).ranking);
59
+ }
60
+ exports.rankings = rankings;
61
+ function factions(G) {
62
+ return G.players.map((pl) => engine.playerColors[pl.id]);
63
+ }
64
+ exports.factions = factions;
65
+ function replay(G, { to = Infinity }) {
66
+ const oldPlayers = G.players;
67
+ const oldG = G;
68
+ G = engine.setup(G.players.length, G.options, G.seed);
69
+ for (let i = 0; i < oldPlayers.length && i < G.players.length; i++) {
70
+ G.players[i].name = oldPlayers[i].name;
71
+ }
72
+ for (const move of oldG.log.slice(0, to).filter((event) => event.type === 'move')) {
73
+ utils_1.asserts(move);
74
+ G = engine.move(G, move.move, move.player);
75
+ }
76
+ return G;
77
+ }
78
+ exports.replay = replay;
79
+ function round(G) {
80
+ return G.round;
81
+ }
82
+ exports.round = round;
83
+ async function dropPlayer(G, playerNum) {
84
+ const player = G.players[playerNum];
85
+ player.isDropped = true;
86
+ G.log.push({
87
+ type: 'event',
88
+ event: `Player ${playerNum} was dropped`,
89
+ });
90
+ if (player.availableMoves[move_1.MoveName.Pass]) {
91
+ G = engine.move(G, { name: move_1.MoveName.Pass, data: true }, playerNum);
92
+ }
93
+ else {
94
+ while (G.currentPlayers.includes(playerNum)) {
95
+ G = engine.moveAI(G, playerNum);
96
+ }
97
+ }
98
+ return G;
99
+ }
100
+ exports.dropPlayer = dropPlayer;
101
+ function currentPlayer(G) {
102
+ return G.currentPlayers;
103
+ }
104
+ exports.currentPlayer = currentPlayer;
105
+ function messages(G) {
106
+ return {
107
+ messages: [],
108
+ data: G,
109
+ };
110
+ }
111
+ exports.messages = messages;
112
+ function logLength(G, _player) {
113
+ return G.log.length;
114
+ }
115
+ exports.logLength = logLength;
116
+ function logSlice(G, options) {
117
+ const stripped = engine.stripSecret(G, options === null || options === void 0 ? void 0 : options.player);
118
+ return {
119
+ log: stripped.log.slice(options === null || options === void 0 ? void 0 : options.start, options === null || options === void 0 ? void 0 : options.end),
120
+ availableMoves: (options === null || options === void 0 ? void 0 : options.end) === undefined
121
+ ? stripped.players.map((pl) => pl.availableMoves)
122
+ : engine
123
+ .stripSecret(replay(G, { to: options.end }), options.player)
124
+ .players.map((pl) => pl.availableMoves),
125
+ };
126
+ }
127
+ exports.logSlice = logSlice;
package/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { availableMoves, AvailableMoves } from './src/available-moves';
2
+ export { currentPlayers, ended, move, playersSortedByScore, reconstructState, setup, stripSecret } from './src/engine';
3
+ export { Phase } from './src/gamestate';
4
+ export type { GameState, Player } from './src/gamestate';
5
+ export { LogItem } from './src/log';
6
+ export { Move, MoveName, Moves } from './src/move';
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "powergrid-engine",
3
+ "version": "1.9.7",
4
+ "description": "An engine for Power Grid",
5
+ "main": "dist/index.js",
6
+ "types": "index.ts",
7
+ "source": "index.ts",
8
+ "repository": "git@github.com:LucasAMello/powergrid-engine.git",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "author": "LucasAMello",
13
+ "license": "MIT",
14
+ "dependencies": {
15
+ "delaunator": "^4.0.0",
16
+ "lodash": "^4.17.15",
17
+ "seedrandom": "^3.0.5"
18
+ },
19
+ "devDependencies": {
20
+ "@types/chai": "^4.1.2",
21
+ "@types/delaunator": "^5.0.0",
22
+ "@types/lodash": "^4.14.149",
23
+ "@types/mocha": "^2.2.48",
24
+ "@types/node": "^13.11.0",
25
+ "@types/seedrandom": "^2.4.27",
26
+ "@typescript-eslint/eslint-plugin": "^4.25.0",
27
+ "@typescript-eslint/parser": "^4.25.0",
28
+ "chai": "^4.1.2",
29
+ "eslint": "^6.8.0",
30
+ "eslint-plugin-import": "^2.20.2",
31
+ "eslint-plugin-node": "^11.1.0",
32
+ "eslint-plugin-promise": "^4.2.1",
33
+ "eslint-plugin-standard": "^4.0.1",
34
+ "mocha": "^5.0.4",
35
+ "ts-node": "^10.0.0",
36
+ "typescript": "^4.3.5"
37
+ },
38
+ "files": [
39
+ "index.ts",
40
+ "wrapper.ts",
41
+ "src",
42
+ "dist",
43
+ "tsconfig.json"
44
+ ],
45
+ "scripts": {
46
+ "build": "npm run clean && tsc",
47
+ "clean": "rm -Rf dist",
48
+ "lint": "eslint . --ext .ts",
49
+ "test": "mocha -r ts-node/register src/*.spec.ts"
50
+ }
51
+ }