snakeia-server 1.1.5 → 1.2.1

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.
package/.drone.yml CHANGED
@@ -15,4 +15,5 @@ steps:
15
15
  password:
16
16
  from_secret: REGISTRY_PASSWORD
17
17
  tags:
18
- - latest
18
+ - latest
19
+ - 1.2.1
@@ -23,20 +23,57 @@ const Position = snakeia.Position;
23
23
  const Grid = snakeia.Grid;
24
24
  const Snake = snakeia.Snake;
25
25
  const GameEngine = snakeia.GameEngine;
26
- const seedrandom = require("seedrandom");
27
26
 
28
27
  let game;
29
28
 
30
29
  function copySnakes(snakes) {
31
- var copy = JSON.parse(JSON.stringify(snakes));
30
+ const snakesCopy = [];
32
31
 
33
- if(copy) {
34
- for(var i = 0; i < copy.length; i++) {
35
- delete copy[i]["grid"];
36
- }
32
+ if(snakes) {
33
+ snakes.forEach(snake => {
34
+ if(snake) {
35
+ const snakeCopy = new Snake();
36
+
37
+ snakeCopy.color = snake.color;
38
+ snakeCopy.direction = snake.direction;
39
+ snakeCopy.errorInit = snake.errorInit;
40
+ snakeCopy.gameOver = snake.gameOver;
41
+ snakeCopy.autoRetry = snake.autoRetry;
42
+ snakeCopy.aiLevel = snake.aiLevel;
43
+
44
+ if(snake.lastTail) {
45
+ snakeCopy.lastTail = JSON.parse(JSON.stringify(snake.lastTail));
46
+ }
47
+
48
+ if(snake.lastHead) {
49
+ snakeCopy.lastHead = JSON.parse(JSON.stringify(snake.lastHead));
50
+ }
51
+
52
+ snakeCopy.lastTailMoved = snake.lastTailMoved;
53
+ snakeCopy.lastHeadMoved = snake.lastHeadMoved;
54
+ snakeCopy.name = snake.name;
55
+ snakeCopy.player = snake.player;
56
+
57
+ if(snake.queue) {
58
+ snakeCopy.queue = JSON.parse(JSON.stringify(snake.queue));
59
+ }
60
+
61
+ snakeCopy.score = snake.score;
62
+ snakeCopy.scoreMax = snake.scoreMax;
63
+ snakeCopy.ticksDead = snake.ticksDead;
64
+ snakeCopy.ticksWithoutAction = snake.ticksWithoutAction;
65
+ snakeCopy.grid = null;
66
+
67
+ if(snake.snakeAI && snake.snakeAI.aiLevelText) {
68
+ snakeCopy.snakeAI.aiLevelText = snake.snakeAI.aiLevelText;
69
+ }
70
+
71
+ snakesCopy.push(snakeCopy);
72
+ }
73
+ });
37
74
  }
38
75
 
39
- return copy;
76
+ return snakesCopy;
40
77
  }
41
78
 
42
79
  function copyGrid(grid) {
@@ -51,35 +88,22 @@ function copyGrid(grid) {
51
88
  }
52
89
 
53
90
  function parseSnakes(snakes, grid) {
54
- if(game) {
55
- var grid = grid != null ? grid : game.grid;
56
- }
57
-
58
- grid = Object.assign(new Grid(), grid);
59
-
60
- if(!snakes && game) {
61
- snakes = game.snakes;
62
- }
63
-
64
- for(var i = 0; i < snakes.length; i++) {
65
- snakes[i].grid = grid;
66
- snakes[i] = Object.assign(new Snake(), snakes[i]);
67
-
68
- for(var j = 0; j < snakes[i].queue.length; j++) {
69
- snakes[i].queue[j] = Object.assign(new Position(), snakes[i].queue[j]);
70
- }
91
+ let gridCopy = game ? (grid ?? game.grid) : grid;
92
+ gridCopy = Object.assign(new Grid(), gridCopy);
71
93
 
72
- snakes[i].initAI();
73
- }
94
+ snakes = snakes ?? game?.snakes;
95
+ const snakesCopy = (Array.isArray(snakes) ? snakes : [snakes]).map(snake => {
96
+ const newSnake = Object.assign(new Snake(), snake);
97
+ newSnake.grid = gridCopy;
98
+ newSnake.queue = newSnake.queue.map(pos => Object.assign(new Position(), pos));
99
+ return newSnake;
100
+ });
74
101
 
75
- return {
76
- grid: grid,
77
- snakes: snakes
78
- };
102
+ return { grid: gridCopy, snakes: snakesCopy };
79
103
  }
80
104
 
81
105
  if(!isMainThread) {
82
- parentPort.on("message", (data) => {
106
+ parentPort.on("message", async data => {
83
107
  const type = data.type;
84
108
  const keys = Object.keys(data);
85
109
 
@@ -89,19 +113,17 @@ if(!isMainThread) {
89
113
  const snakes = parsed["snakes"];
90
114
 
91
115
  if(!game) {
92
- game = new GameEngine(grid, snakes, data.speed, data.enablePause, data.enableRetry, data.progressiveSpeed);
93
- game.init();
94
-
95
- parentPort.postMessage({
96
- type: "init",
97
- "snakes": copySnakes(game.snakes),
98
- "grid": copyGrid(game.grid),
99
- "enablePause": game.enablePause,
100
- "enableRetry": game.enableRetry,
101
- "progressiveSpeed": game.progressiveSpeed,
102
- "offsetFrame": game.speed * GameConstants.Setting.TIME_MULTIPLIER,
103
- "errorOccurred": game.errorOccurred
104
- });
116
+ try {
117
+ game = new GameEngine(grid, snakes, data.speed, data.enablePause, data.enableRetry, data.progressiveSpeed, data.aiStuckLimit, data.disableStuckAIDetection, data.aiUltraModelSettings);
118
+ await initGame();
119
+ } catch(e) {
120
+ console.error(e);
121
+
122
+ parentPort.postMessage({
123
+ type: "init",
124
+ errorOccurred: true
125
+ });
126
+ }
105
127
 
106
128
  game.onReset(() => {
107
129
  parentPort.postMessage({
@@ -127,7 +149,8 @@ if(!isMainThread) {
127
149
  "getInfosGame": false,
128
150
  "errorOccurred": game.errorOccurred,
129
151
  "aiStuck": game.aiStuck,
130
- "precAiStuck": false
152
+ "precAiStuck": false,
153
+ "engineLoading": game.engineLoading
131
154
  });
132
155
  });
133
156
 
@@ -144,7 +167,8 @@ if(!isMainThread) {
144
167
  "confirmExit": false,
145
168
  "getInfos": false,
146
169
  "getInfosGame": false,
147
- "errorOccurred": game.errorOccurred
170
+ "errorOccurred": game.errorOccurred,
171
+ "engineLoading": game.engineLoading
148
172
  });
149
173
  });
150
174
 
@@ -156,7 +180,8 @@ if(!isMainThread) {
156
180
  "confirmExit": false,
157
181
  "getInfos": false,
158
182
  "getInfosGame": false,
159
- "errorOccurred": game.errorOccurred
183
+ "errorOccurred": game.errorOccurred,
184
+ "engineLoading": game.engineLoading
160
185
  });
161
186
  });
162
187
 
@@ -167,7 +192,8 @@ if(!isMainThread) {
167
192
  "confirmExit": false,
168
193
  "getInfos": false,
169
194
  "getInfosGame": false,
170
- "errorOccurred": game.errorOccurred
195
+ "errorOccurred": game.errorOccurred,
196
+ "engineLoading": game.engineLoading
171
197
  });
172
198
  });
173
199
 
@@ -182,7 +208,8 @@ if(!isMainThread) {
182
208
  "confirmExit": false,
183
209
  "getInfos": false,
184
210
  "getInfosGame": false,
185
- "errorOccurred": game.errorOccurred
211
+ "errorOccurred": game.errorOccurred,
212
+ "engineLoading": game.engineLoading
186
213
  });
187
214
  });
188
215
 
@@ -197,7 +224,8 @@ if(!isMainThread) {
197
224
  "confirmExit": false,
198
225
  "getInfos": false,
199
226
  "getInfosGame": false,
200
- "errorOccurred": game.errorOccurred
227
+ "errorOccurred": game.errorOccurred,
228
+ "engineLoading": game.engineLoading
201
229
  });
202
230
  });
203
231
 
@@ -214,7 +242,8 @@ if(!isMainThread) {
214
242
  "confirmExit": false,
215
243
  "getInfos": false,
216
244
  "getInfosGame": false,
217
- "errorOccurred": game.errorOccurred
245
+ "errorOccurred": game.errorOccurred,
246
+ "engineLoading": game.engineLoading
218
247
  });
219
248
  });
220
249
 
@@ -243,7 +272,8 @@ if(!isMainThread) {
243
272
  "numFruit": game.numFruit,
244
273
  "offsetFrame": 0,
245
274
  "errorOccurred": game.errorOccurred,
246
- "aiStuck": game.aiStuck
275
+ "aiStuck": game.aiStuck,
276
+ "engineLoading": game.engineLoading
247
277
  });
248
278
  });
249
279
 
@@ -266,14 +296,25 @@ if(!isMainThread) {
266
296
  "speed": game.speed,
267
297
  "countBeforePlay": game.countBeforePlay,
268
298
  "numFruit": game.numFruit,
269
- "errorOccurred": game.errorOccurred
299
+ "errorOccurred": game.errorOccurred,
300
+ "engineLoading": game.engineLoading
270
301
  });
271
302
  });
272
303
  } else {
273
304
  game.snakes = snakes;
274
305
  game.grid = grid;
275
306
  game.countBeforePlay = 3;
276
- game.init();
307
+
308
+ try {
309
+ await initGame();
310
+ } catch(e) {
311
+ console.error(e);
312
+
313
+ parentPort.postMessage({
314
+ type: "init",
315
+ errorOccurred: true
316
+ });
317
+ }
277
318
  }
278
319
  } else if(game) {
279
320
  switch(type) {
@@ -347,4 +388,20 @@ if(!isMainThread) {
347
388
  }
348
389
  }
349
390
  });
391
+ }
392
+
393
+ async function initGame() {
394
+ await game.init();
395
+
396
+ parentPort.postMessage({
397
+ type: "init",
398
+ "snakes": copySnakes(game.snakes),
399
+ "grid": copyGrid(game.grid),
400
+ "enablePause": game.enablePause,
401
+ "enableRetry": game.enableRetry,
402
+ "progressiveSpeed": game.progressiveSpeed,
403
+ "offsetFrame": game.speed * GameConstants.Setting.TIME_MULTIPLIER,
404
+ "errorOccurred": game.errorOccurred,
405
+ "engineLoading": game.engineLoading
406
+ });
350
407
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright (C) 2020 Eliastik (eliastiksofts.com)
2
+ * Copyright (C) 2020-2025 Eliastik (eliastiksofts.com)
3
3
  *
4
4
  * This file is part of "SnakeIA Server".
5
5
  *
@@ -16,7 +16,7 @@
16
16
  * You should have received a copy of the GNU General Public License
17
17
  * along with "SnakeIA Server". If not, see <http://www.gnu.org/licenses/>.
18
18
  */
19
- const { Worker } = require('worker_threads');
19
+ const { Worker } = require("worker_threads");
20
20
  const snakeia = require("snakeia");
21
21
  const GameEngine = snakeia.GameEngine;
22
22
  const Grid = snakeia.Grid;
@@ -25,132 +25,157 @@ const Position = snakeia.Position;
25
25
  let logger;
26
26
 
27
27
  class GameEngineMultithreadingController extends GameEngine {
28
- constructor(grid, snakes, speed, enablePause, enableRetry, progressiveSpeed) {
29
- super(grid, snakes, speed, enablePause, enableRetry, progressiveSpeed);
28
+
29
+ constructor(grid, snakes, speed, enablePause, enableRetry, progressiveSpeed, aiStuckLimit, disableStuckAIDetection, aiUltraModelSettings) {
30
+ super(grid, snakes, speed, enablePause, enableRetry, progressiveSpeed, aiStuckLimit, disableStuckAIDetection, aiUltraModelSettings);
30
31
  this.worker = new Worker("./GameEngineMultithreading.js");
32
+ this.workerReady = false;
33
+ this.messageQueue = []; // Queue of message if the worker is still loading
31
34
  this.eventsInit = false;
32
35
  }
33
36
 
34
- init() {
37
+ async init() {
35
38
  if(!this.eventsInit) {
36
- this.worker.on("message", (data) => {
37
- const type = data.type;
38
- const dataKeys = Object.keys(data);
39
+ this.initEventHandlers();
40
+ }
39
41
 
40
- if(dataKeys.length > 1) {
41
- let grid = this.grid;
42
- let snakes = this.snakes;
42
+ if(this.grid) {
43
+ this.grid.rngGrid = null;
44
+ this.grid.rngGame = null;
45
+ }
43
46
 
44
- if(data.grid) {
45
- grid = Object.assign(new Grid(), data.grid);
46
- data.grid = grid;
47
- }
48
-
49
- if(data.snakes) {
50
- for(let i = 0; i < data.snakes.length; i++) {
51
- data.snakes[i].grid = grid;
52
- data.snakes[i] = Object.assign(new Snake(), data.snakes[i]);
53
-
54
- for(let j = 0; j < data.snakes[i].queue.length; j++) {
55
- data.snakes[i].queue[j] = Object.assign(new Position(), data.snakes[i].queue[j]);
56
- }
57
- }
47
+ return new Promise(resolve => {
48
+ const handleMessage = (data) => {
49
+ if(data.type === "init") {
50
+ this.worker.off("message", handleMessage);
51
+ resolve();
52
+ }
53
+ };
54
+
55
+ this.worker.on("message", handleMessage);
56
+
57
+ this.worker.postMessage({
58
+ type: "init",
59
+ grid: this.grid,
60
+ snakes: this.snakes,
61
+ speed: this.speed,
62
+ enablePause: this.enablePause,
63
+ enableRetry: this.enableRetry,
64
+ progressiveSpeed: this.progressiveSpeed,
65
+ aiStuckLimit: this.aiStuckLimit,
66
+ disableStuckAIDetection: this.disableStuckAIDetection,
67
+ aiUltraModelSettings: this.aiUltraModelSettings
68
+ });
69
+ });
70
+ }
58
71
 
59
- snakes = data.snakes;
60
- }
72
+ initEventHandlers() {
73
+ this.worker.on("message", data => {
74
+ const type = data.type;
75
+ const dataKeys = Object.keys(data);
76
+
77
+ if (dataKeys.length > 1) {
78
+ let grid = this.grid;
79
+ let snakes = this.snakes;
80
+
81
+ if (data.grid) {
82
+ grid = Object.assign(new Grid(), data.grid);
83
+ data.grid = grid;
84
+ }
61
85
 
62
- this.snakes = snakes;
63
- this.grid = grid;
86
+ if (data.snakes) {
87
+ for (let i = 0; i < data.snakes.length; i++) {
88
+ data.snakes[i].grid = grid;
89
+ data.snakes[i] = Object.assign(new Snake(), data.snakes[i]);
64
90
 
65
- for(let i = 0; i < dataKeys.length; i++) {
66
- if(dataKeys[i] != "snakes" && dataKeys[i] != "grid") {
67
- this[dataKeys[i]] = data[dataKeys[i]];
91
+ for (let j = 0; j < data.snakes[i].queue.length; j++) {
92
+ data.snakes[i].queue[j] = Object.assign(new Position(), data.snakes[i].queue[j]);
68
93
  }
69
94
  }
70
- }
71
95
 
72
- switch(type) {
73
- case "reset":
74
- this.reactor.dispatchEvent("onReset");
75
- break;
76
- case "start":
77
- this.reactor.dispatchEvent("onStart");
78
- break;
79
- case "pause":
80
- this.reactor.dispatchEvent("onPause");
81
- break;
82
- case "continue":
83
- this.reactor.dispatchEvent("onContinue");
84
- break;
85
- case "stop":
86
- this.reactor.dispatchEvent("onStop");
87
- break;
88
- case "exit":
89
- this.reactor.dispatchEvent("onExit");
90
- break;
91
- case "kill":
92
- this.reactor.dispatchEvent("onKill");
93
- break;
94
- case "scoreIncreased":
95
- this.reactor.dispatchEvent("onScoreIncreased");
96
- break;
97
- case "update":
98
- this.reactor.dispatchEvent("onUpdate");
99
- break;
100
- case "updateCounter":
101
- this.reactor.dispatchEvent("onUpdateCounter");
102
- break;
96
+ snakes = data.snakes;
103
97
  }
104
- });
105
-
106
- this.worker.on("error", (error) => {
107
- if(logger) logger.error("Error in GameEngineMultithreading", error);
108
- this.errorOccurred = true;
109
- this.reactor.dispatchEvent("onStop");
110
- });
111
-
112
- this.worker.on("exit", () => {
113
- this.gameFinished = true;
114
- this.reactor.dispatchEvent("onStop");
115
- });
116
98
 
117
- this.eventsInit = true;
118
- }
99
+ this.snakes = snakes;
100
+ this.grid = grid;
119
101
 
120
- if(this.grid) {
121
- this.grid.rngGrid = null;
122
- this.grid.rngGame = null;
123
- }
102
+ for (let i = 0; i < dataKeys.length; i++) {
103
+ if (dataKeys[i] != "snakes" && dataKeys[i] != "grid") {
104
+ this[dataKeys[i]] = data[dataKeys[i]];
105
+ }
106
+ }
107
+ }
108
+
109
+ switch (type) {
110
+ case "init":
111
+ this.workerReady = true;
112
+ this.passQueuedMessages();
113
+ break;
114
+ case "reset":
115
+ this.reactor.dispatchEvent("onReset");
116
+ break;
117
+ case "start":
118
+ this.reactor.dispatchEvent("onStart");
119
+ break;
120
+ case "pause":
121
+ this.reactor.dispatchEvent("onPause");
122
+ break;
123
+ case "continue":
124
+ this.reactor.dispatchEvent("onContinue");
125
+ break;
126
+ case "stop":
127
+ this.reactor.dispatchEvent("onStop");
128
+ break;
129
+ case "exit":
130
+ this.reactor.dispatchEvent("onExit");
131
+ break;
132
+ case "kill":
133
+ this.reactor.dispatchEvent("onKill");
134
+ break;
135
+ case "scoreIncreased":
136
+ this.reactor.dispatchEvent("onScoreIncreased");
137
+ break;
138
+ case "update":
139
+ this.reactor.dispatchEvent("onUpdate");
140
+ break;
141
+ case "updateCounter":
142
+ this.reactor.dispatchEvent("onUpdateCounter");
143
+ break;
144
+ }
145
+ });
146
+
147
+ this.worker.on("error", (error) => {
148
+ if (logger) logger.error("Error in GameEngineMultithreading", error);
149
+ this.errorOccurred = true;
150
+ this.reactor.dispatchEvent("onStop");
151
+ });
124
152
 
125
- this.worker.postMessage({
126
- type: "init",
127
- grid: this.grid,
128
- snakes: this.snakes,
129
- speed: this.speed,
130
- enablePause: this.enablePause,
131
- enableRetry: this.enableRetry,
132
- progressiveSpeed: this.progressiveSpeed
153
+ this.worker.on("exit", () => {
154
+ this.gameFinished = true;
155
+ this.reactor.dispatchEvent("onStop");
133
156
  });
157
+
158
+ this.eventsInit = true;
134
159
  }
135
160
 
136
161
  reset() {
137
- if(this.worker) this.worker.postMessage({ type: "reset" });
162
+ this.passMessage({ type: "reset" });
138
163
  }
139
164
 
140
165
  start() {
141
- if(this.worker) this.worker.postMessage({ type: "start" });
166
+ this.passMessage({ type: "start" });
142
167
  }
143
168
 
144
169
  stop(finish) {
145
- if(this.worker) this.worker.postMessage({ type: finish ? "finish" : "stop" });
170
+ this.passMessage({ type: finish ? "finish" : "stop" });
146
171
  }
147
172
 
148
173
  finish(finish) {
149
- if(this.worker) this.worker.postMessage({ type: finish ? "finish" : "stop" });
174
+ this.passMessage({ type: finish ? "finish" : "stop" });
150
175
  }
151
176
 
152
177
  pause() {
153
- if(this.worker) this.worker.postMessage({ type: "pause" });
178
+ this.passMessage({ type: "pause" });
154
179
  }
155
180
 
156
181
  kill() {
@@ -162,27 +187,27 @@ class GameEngineMultithreadingController extends GameEngine {
162
187
  }
163
188
 
164
189
  tick() {
165
- if(this.worker) this.worker.postMessage({ type: "tick" });
190
+ this.passMessage({ type: "tick" });
166
191
  }
167
192
 
168
193
  exit() {
169
- if(this.worker) this.worker.postMessage({ type: "exit" });
194
+ this.passMessage({ type: "exit" });
170
195
  }
171
196
 
172
197
  key(key, numSnake) {
173
- if(this.worker) this.worker.postMessage({ type: "key", key: key, numSnake: numSnake });
198
+ this.passMessage({ type: "key", key: key, numSnake: numSnake });
174
199
  }
175
200
 
176
201
  setGameOver(numSnake) {
177
- if(this.worker) this.worker.postMessage({ type: "setGameOver", numSnake: numSnake });
202
+ this.passMessage({ type: "setGameOver", numSnake: numSnake });
178
203
  }
179
204
 
180
205
  forceStart() {
181
- if(this.worker) this.worker.postMessage({ type: "forceStart" });
206
+ this.passMessage({ type: "forceStart" });
182
207
  }
183
208
 
184
209
  updateEngine(key, data) {
185
- if(this.worker) this.worker.postMessage({ type: "update", key: key, data: data });
210
+ this.passMessage({ type: "update", key: key, data: data });
186
211
  }
187
212
 
188
213
  onReset(callback) {
@@ -224,6 +249,24 @@ class GameEngineMultithreadingController extends GameEngine {
224
249
  onUpdateCounter(callback) {
225
250
  this.reactor.addEventListener("onUpdateCounter", callback);
226
251
  }
252
+
253
+ passMessage(message) {
254
+ if(this.workerReady && this.worker) {
255
+ this.worker.postMessage(message);
256
+ } else {
257
+ this.messageQueue.push(message);
258
+ }
259
+ }
260
+
261
+ passQueuedMessages() {
262
+ if(this.workerReady && this.worker) {
263
+ this.messageQueue.forEach(message => {
264
+ this.worker.postMessage(message);
265
+ });
266
+
267
+ this.messageQueue = [];
268
+ }
269
+ }
227
270
  }
228
271
 
229
272
  module.exports = function(l) {