snakeia-server 1.1.3-3

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/.dockerignore ADDED
@@ -0,0 +1,2 @@
1
+ node_modules
2
+ npm-debug.log
package/.drone.yml ADDED
@@ -0,0 +1,18 @@
1
+ kind: pipeline
2
+ name: default
3
+
4
+ steps:
5
+ - name: build
6
+ image: node
7
+ commands:
8
+ - npm install
9
+ - name: publish
10
+ image: plugins/docker
11
+ settings:
12
+ repo: eliastik/snakeia-server
13
+ username:
14
+ from_secret: REGISTRY_USER
15
+ password:
16
+ from_secret: REGISTRY_PASSWORD
17
+ tags:
18
+ - latest
package/Dockerfile ADDED
@@ -0,0 +1,10 @@
1
+ FROM node:20-alpine
2
+ RUN addgroup -S snakeia-server && adduser -S snakeia-server -G snakeia-server && chown -R snakeia-server:snakeia-server /home/snakeia-server
3
+ RUN apk add git
4
+ WORKDIR /home/snakeia-server/server
5
+ COPY package*.json ./
6
+ COPY . .
7
+ RUN chown -R snakeia-server:snakeia-server /home/snakeia-server
8
+ USER snakeia-server
9
+ RUN npm install
10
+ ENTRYPOINT npm run start
@@ -0,0 +1,350 @@
1
+ /*
2
+ * Copyright (C) 2020 Eliastik (eliastiksofts.com)
3
+ *
4
+ * This file is part of "SnakeIA Server".
5
+ *
6
+ * "SnakeIA Server" is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * "SnakeIA Server" is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with "SnakeIA Server". If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+ const { isMainThread, parentPort } = require('worker_threads');
20
+ const snakeia = require("snakeia");
21
+ const GameConstants = snakeia.GameConstants;
22
+ const Position = snakeia.Position;
23
+ const Grid = snakeia.Grid;
24
+ const Snake = snakeia.Snake;
25
+ const GameEngine = snakeia.GameEngine;
26
+ const seedrandom = require("seedrandom");
27
+
28
+ let game;
29
+
30
+ function copySnakes(snakes) {
31
+ var copy = JSON.parse(JSON.stringify(snakes));
32
+
33
+ if(copy) {
34
+ for(var i = 0; i < copy.length; i++) {
35
+ delete copy[i]["grid"];
36
+ }
37
+ }
38
+
39
+ return copy;
40
+ }
41
+
42
+ function copyGrid(grid) {
43
+ var copy = JSON.parse(JSON.stringify(grid));
44
+
45
+ if(copy) {
46
+ copy.rngGrid = null;
47
+ copy.rngGame = null;
48
+ }
49
+
50
+ return copy;
51
+ }
52
+
53
+ 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
+ }
71
+
72
+ snakes[i].initAI();
73
+ }
74
+
75
+ return {
76
+ grid: grid,
77
+ snakes: snakes
78
+ };
79
+ }
80
+
81
+ if(!isMainThread) {
82
+ parentPort.on("message", (data) => {
83
+ const type = data.type;
84
+ const keys = Object.keys(data);
85
+
86
+ if(type == "init") {
87
+ const parsed = parseSnakes(data.snakes, data.grid);
88
+ const grid = parsed["grid"];
89
+ const snakes = parsed["snakes"];
90
+
91
+ 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
+ });
105
+
106
+ game.onReset(() => {
107
+ parentPort.postMessage({
108
+ type: "reset",
109
+ "paused": game.paused,
110
+ "isReseted": game.isReseted,
111
+ "exited": game.exited,
112
+ "snakes": copySnakes(game.snakes),
113
+ "grid": copyGrid(game.grid),
114
+ "numFruit": game.numFruit,
115
+ "ticks": game.ticks,
116
+ "scoreMax": game.scoreMax,
117
+ "gameOver": game.gameOver,
118
+ "gameFinished": game.gameFinished,
119
+ "gameMazeWin": game.gameMazeWin,
120
+ "starting": game.starting,
121
+ "initialSpeed": game.initialSpeed,
122
+ "speed": game.speed,
123
+ "offsetFrame": game.speed * GameConstants.Setting.TIME_MULTIPLIER,
124
+ "confirmReset": false,
125
+ "confirmExit": false,
126
+ "getInfos": false,
127
+ "getInfosGame": false,
128
+ "errorOccurred": game.errorOccurred,
129
+ "aiStuck": game.aiStuck,
130
+ "precAiStuck": false
131
+ });
132
+ });
133
+
134
+ game.onStart(() => {
135
+ parentPort.postMessage({
136
+ type: "start",
137
+ "snakes": copySnakes(game.snakes),
138
+ "grid": copyGrid(game.grid),
139
+ "starting": game.starting,
140
+ "countBeforePlay": game.countBeforePlay,
141
+ "paused": game.paused,
142
+ "isReseted": game.isReseted,
143
+ "confirmReset": false,
144
+ "confirmExit": false,
145
+ "getInfos": false,
146
+ "getInfosGame": false,
147
+ "errorOccurred": game.errorOccurred
148
+ });
149
+ });
150
+
151
+ game.onPause(() => {
152
+ parentPort.postMessage({
153
+ type: "pause",
154
+ "paused": game.paused,
155
+ "confirmReset": false,
156
+ "confirmExit": false,
157
+ "getInfos": false,
158
+ "getInfosGame": false,
159
+ "errorOccurred": game.errorOccurred
160
+ });
161
+ });
162
+
163
+ game.onContinue(() => {
164
+ parentPort.postMessage({
165
+ type: "continue",
166
+ "confirmReset": false,
167
+ "confirmExit": false,
168
+ "getInfos": false,
169
+ "getInfosGame": false,
170
+ "errorOccurred": game.errorOccurred
171
+ });
172
+ });
173
+
174
+ game.onStop(() => {
175
+ parentPort.postMessage({
176
+ type: "stop",
177
+ "paused": game.paused,
178
+ "scoreMax": game.scoreMax,
179
+ "gameOver": game.gameOver,
180
+ "gameFinished": game.gameFinished,
181
+ "confirmReset": false,
182
+ "confirmExit": false,
183
+ "getInfos": false,
184
+ "getInfosGame": false,
185
+ "errorOccurred": game.errorOccurred
186
+ });
187
+ });
188
+
189
+ game.onExit(() => {
190
+ parentPort.postMessage({
191
+ type: "exit",
192
+ "paused": game.paused,
193
+ "gameOver": game.gameOver,
194
+ "gameFinished": game.gameFinished,
195
+ "exited": game.exited,
196
+ "confirmReset": false,
197
+ "confirmExit": false,
198
+ "getInfos": false,
199
+ "getInfosGame": false,
200
+ "errorOccurred": game.errorOccurred
201
+ });
202
+ });
203
+
204
+ game.onKill(() => {
205
+ parentPort.postMessage({
206
+ type: "kill",
207
+ "paused": game.paused,
208
+ "gameOver": game.gameOver,
209
+ "killed": game.killed,
210
+ "snakes": copySnakes(game.snakes),
211
+ "grid": copyGrid(game.grid),
212
+ "gameFinished": game.gameFinished,
213
+ "confirmReset": false,
214
+ "confirmExit": false,
215
+ "getInfos": false,
216
+ "getInfosGame": false,
217
+ "errorOccurred": game.errorOccurred
218
+ });
219
+ });
220
+
221
+ game.onScoreIncreased(() => {
222
+ parentPort.postMessage({ type: "scoreIncreased" });
223
+ });
224
+
225
+ game.onUpdate(() => {
226
+ parentPort.postMessage({
227
+ type: "update",
228
+ "paused": game.paused,
229
+ "isReseted": game.isReseted,
230
+ "exited": game.exited,
231
+ "snakes": copySnakes(game.snakes),
232
+ "grid": copyGrid(game.grid),
233
+ "numFruit": game.numFruit,
234
+ "ticks": game.ticks,
235
+ "scoreMax": game.scoreMax,
236
+ "gameOver": game.gameOver,
237
+ "gameFinished": game.gameFinished,
238
+ "gameMazeWin": game.gameMazeWin,
239
+ "starting": game.starting,
240
+ "initialSpeed": game.initialSpeed,
241
+ "speed": game.speed,
242
+ "countBeforePlay": game.countBeforePlay,
243
+ "numFruit": game.numFruit,
244
+ "offsetFrame": 0,
245
+ "errorOccurred": game.errorOccurred,
246
+ "aiStuck": game.aiStuck
247
+ });
248
+ });
249
+
250
+ game.onUpdateCounter(() => {
251
+ parentPort.postMessage({
252
+ type: "updateCounter",
253
+ "paused": game.paused,
254
+ "isReseted": game.isReseted,
255
+ "exited": game.exited,
256
+ "snakes": copySnakes(game.snakes),
257
+ "grid": copyGrid(game.grid),
258
+ "numFruit": game.numFruit,
259
+ "ticks": game.ticks,
260
+ "scoreMax": game.scoreMax,
261
+ "gameOver": game.gameOver,
262
+ "gameFinished": game.gameFinished,
263
+ "gameMazeWin": game.gameMazeWin,
264
+ "starting": game.starting,
265
+ "initialSpeed": game.initialSpeed,
266
+ "speed": game.speed,
267
+ "countBeforePlay": game.countBeforePlay,
268
+ "numFruit": game.numFruit,
269
+ "errorOccurred": game.errorOccurred
270
+ });
271
+ });
272
+ } else {
273
+ game.snakes = snakes;
274
+ game.grid = grid;
275
+ game.countBeforePlay = 3;
276
+ game.init();
277
+ }
278
+ } else if(game) {
279
+ switch(type) {
280
+ case "reset":
281
+ game.reset();
282
+ break;
283
+ case "start":
284
+ game.start();
285
+ break;
286
+ case "stop":
287
+ game.stop();
288
+ break;
289
+ case "finish":
290
+ game.stop(true);
291
+ break;
292
+ case "stop":
293
+ game.stop(false);
294
+ break;
295
+ case "pause":
296
+ game.pause();
297
+ break;
298
+ case "kill":
299
+ game.kill();
300
+ break;
301
+ case "tick":
302
+ game.paused = false;
303
+ game.countBeforePlay = -1;
304
+ game.tick();
305
+ break;
306
+ case "exit":
307
+ game.exit();
308
+ break;
309
+ case "forceStart":
310
+ game.forceStart();
311
+ break;
312
+ case "key":
313
+ if(keys.length > 1) {
314
+ const key = data.key;
315
+ const numSnake = data.numSnake;
316
+
317
+ var playerSnake = game.getPlayer(numSnake, GameConstants.PlayerType.HUMAN) || game.getPlayer(numSnake, GameConstants.PlayerType.HYBRID_HUMAN_AI);
318
+
319
+ if(playerSnake) {
320
+ playerSnake.lastKey = key;
321
+ }
322
+ }
323
+ break;
324
+ case "setGameOver":
325
+ if(keys.length > 1) {
326
+ const numSnake = data.numSnake;
327
+
328
+ var playerSnake = game.getPlayer(numSnake, GameConstants.PlayerType.HUMAN) || game.getPlayer(numSnake, GameConstants.PlayerType.HYBRID_HUMAN_AI);
329
+
330
+ if(playerSnake) {
331
+ playerSnake.gameOver = true;
332
+ }
333
+ }
334
+ case "init":
335
+ if(data.length > 1) {
336
+ if(data.key == "snakes") {
337
+ var d = parseSnakes(data.data);
338
+ game.snakes = d.snakes;
339
+ } else if(data.key == "grid") {
340
+ var d = parseSnakes(null, data.data);
341
+ game.grid = d.grid;
342
+ } else {
343
+ game[data.key] = data.data;
344
+ }
345
+ }
346
+ break;
347
+ }
348
+ }
349
+ });
350
+ }
@@ -0,0 +1,232 @@
1
+ /*
2
+ * Copyright (C) 2020 Eliastik (eliastiksofts.com)
3
+ *
4
+ * This file is part of "SnakeIA Server".
5
+ *
6
+ * "SnakeIA Server" is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * "SnakeIA Server" is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with "SnakeIA Server". If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+ const { Worker } = require('worker_threads');
20
+ const snakeia = require("snakeia");
21
+ const GameEngine = snakeia.GameEngine;
22
+ const Grid = snakeia.Grid;
23
+ const Snake = snakeia.Snake;
24
+ const Position = snakeia.Position;
25
+ let logger;
26
+
27
+ class GameEngineMultithreadingController extends GameEngine {
28
+ constructor(grid, snakes, speed, enablePause, enableRetry, progressiveSpeed) {
29
+ super(grid, snakes, speed, enablePause, enableRetry, progressiveSpeed);
30
+ this.worker = new Worker("./GameEngineMultithreading.js");
31
+ this.eventsInit = false;
32
+ }
33
+
34
+ init() {
35
+ if(!this.eventsInit) {
36
+ this.worker.on("message", (data) => {
37
+ const type = data.type;
38
+ const dataKeys = Object.keys(data);
39
+
40
+ if(dataKeys.length > 1) {
41
+ let grid = this.grid;
42
+ let snakes = this.snakes;
43
+
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
+ }
58
+
59
+ snakes = data.snakes;
60
+ }
61
+
62
+ this.snakes = snakes;
63
+ this.grid = grid;
64
+
65
+ for(let i = 0; i < dataKeys.length; i++) {
66
+ if(dataKeys[i] != "snakes" && dataKeys[i] != "grid") {
67
+ this[dataKeys[i]] = data[dataKeys[i]];
68
+ }
69
+ }
70
+ }
71
+
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;
103
+ }
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
+
117
+ this.eventsInit = true;
118
+ }
119
+
120
+ if(this.grid) {
121
+ this.grid.rngGrid = null;
122
+ this.grid.rngGame = null;
123
+ }
124
+
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
133
+ });
134
+ }
135
+
136
+ reset() {
137
+ if(this.worker) this.worker.postMessage({ type: "reset" });
138
+ }
139
+
140
+ start() {
141
+ if(this.worker) this.worker.postMessage({ type: "start" });
142
+ }
143
+
144
+ stop(finish) {
145
+ if(this.worker) this.worker.postMessage({ type: finish ? "finish" : "stop" });
146
+ }
147
+
148
+ finish(finish) {
149
+ if(this.worker) this.worker.postMessage({ type: finish ? "finish" : "stop" });
150
+ }
151
+
152
+ pause() {
153
+ if(this.worker) this.worker.postMessage({ type: "pause" });
154
+ }
155
+
156
+ kill() {
157
+ if(this.worker) {
158
+ this.worker.postMessage({ type: "kill" });
159
+ this.worker.terminate();
160
+ this.worker = null;
161
+ }
162
+ }
163
+
164
+ tick() {
165
+ if(this.worker) this.worker.postMessage({ type: "tick" });
166
+ }
167
+
168
+ exit() {
169
+ if(this.worker) this.worker.postMessage({ type: "exit" });
170
+ }
171
+
172
+ key(key, numSnake) {
173
+ if(this.worker) this.worker.postMessage({ type: "key", key: key, numSnake: numSnake });
174
+ }
175
+
176
+ setGameOver(numSnake) {
177
+ if(this.worker) this.worker.postMessage({ type: "setGameOver", numSnake: numSnake });
178
+ }
179
+
180
+ forceStart() {
181
+ if(this.worker) this.worker.postMessage({ type: "forceStart" });
182
+ }
183
+
184
+ updateEngine(key, data) {
185
+ if(this.worker) this.worker.postMessage({ type: "update", key: key, data: data });
186
+ }
187
+
188
+ onReset(callback) {
189
+ this.reactor.addEventListener("onReset", callback);
190
+ }
191
+
192
+ onStart(callback) {
193
+ this.reactor.addEventListener("onStart", callback);
194
+ }
195
+
196
+ onContinue(callback) {
197
+ this.reactor.addEventListener("onContinue", callback);
198
+ }
199
+
200
+ onStop(callback) {
201
+ this.reactor.addEventListener("onStop", callback);
202
+ }
203
+
204
+ onPause(callback) {
205
+ this.reactor.addEventListener("onPause", callback);
206
+ }
207
+
208
+ onExit(callback) {
209
+ this.reactor.addEventListener("onExit", callback);
210
+ }
211
+
212
+ onKill(callback) {
213
+ this.reactor.addEventListener("onKill", callback);
214
+ }
215
+
216
+ onScoreIncreased(callback) {
217
+ this.reactor.addEventListener("onScoreIncreased", callback);
218
+ }
219
+
220
+ onUpdate(callback) {
221
+ this.reactor.addEventListener("onUpdate", callback);
222
+ }
223
+
224
+ onUpdateCounter(callback) {
225
+ this.reactor.addEventListener("onUpdateCounter", callback);
226
+ }
227
+ }
228
+
229
+ module.exports = function(l) {
230
+ logger = l;
231
+ return GameEngineMultithreadingController;
232
+ };