Converted server to Typescript
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import { ToastProgrammatic as Toast } from 'buefy'
|
import { ToastProgrammatic as Toast } from 'buefy'
|
||||||
|
|
||||||
// const connection = new WebSocket('ws://localhost:3000/socket')
|
const server = process.env.SERVER || 'localhost:3000'
|
||||||
const connection = new WebSocket('wss://tronio.gltronic.ovh/socket')
|
const connection = new WebSocket('ws://' + server + '/socket')
|
||||||
|
// const connection = new WebSocket('wss://tronio.gltronic.ovh/socket')
|
||||||
|
|
||||||
export default function createSocketPlugin () {
|
export default function createSocketPlugin () {
|
||||||
return store => {
|
return store => {
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"commonjs": true,
|
|
||||||
"es2021": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"standard"
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 12
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<projectDescription>
|
|
||||||
<name>tronio</name>
|
|
||||||
<comment></comment>
|
|
||||||
<projects>
|
|
||||||
</projects>
|
|
||||||
<buildSpec>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
|
||||||
<natures>
|
|
||||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
|
||||||
</natures>
|
|
||||||
<filteredResources>
|
|
||||||
<filter>
|
|
||||||
<id>1599131612586</id>
|
|
||||||
<name></name>
|
|
||||||
<type>30</type>
|
|
||||||
<matcher>
|
|
||||||
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
|
||||||
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
|
||||||
</matcher>
|
|
||||||
</filter>
|
|
||||||
</filteredResources>
|
|
||||||
</projectDescription>
|
|
||||||
118
server/dist/Game.js
vendored
Normal file
118
server/dist/Game.js
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.Game = void 0;
|
||||||
|
const uuid_1 = require("uuid");
|
||||||
|
const Player_1 = require("./models/Player");
|
||||||
|
const GameSettings_1 = require("./models/GameSettings");
|
||||||
|
class Game {
|
||||||
|
constructor() {
|
||||||
|
this.gameSettings = new GameSettings_1.GameSettings();
|
||||||
|
this.players = new Map();
|
||||||
|
this.sockets = new Map();
|
||||||
|
this.computeUpdates = false;
|
||||||
|
this.updateInterval = setInterval(() => this.step(), 10000000);
|
||||||
|
this.lastUpdateTime = Date.now();
|
||||||
|
this.doUpdate = true;
|
||||||
|
clearInterval(this.updateInterval);
|
||||||
|
}
|
||||||
|
login(connection, name) {
|
||||||
|
console.log('[GAME] Player ' + name + ' connected | ' + this.players.size);
|
||||||
|
const id = uuid_1.v4();
|
||||||
|
connection.id = id;
|
||||||
|
connection.name = name;
|
||||||
|
this.sockets.set(id, connection);
|
||||||
|
this.players.set(id, new Player_1.Player(id, name));
|
||||||
|
connection.send(JSON.stringify({
|
||||||
|
type: 'login',
|
||||||
|
player: this.players.get(id)
|
||||||
|
}));
|
||||||
|
connection.send(JSON.stringify({
|
||||||
|
type: 'gameSettings',
|
||||||
|
gameSettings: this.gameSettings
|
||||||
|
}));
|
||||||
|
if (!this.computeUpdates) {
|
||||||
|
console.log('[GAME] Starting updates');
|
||||||
|
this.updateInterval = setInterval(() => this.step(), 1000 / 60);
|
||||||
|
this.computeUpdates = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logout(connection) {
|
||||||
|
console.log('[GAME] Player ' + connection.name + ' disconnected | ' + this.players.size);
|
||||||
|
this.sockets.delete(connection.id);
|
||||||
|
this.players.delete(connection.id);
|
||||||
|
if (this.players.size === 0) {
|
||||||
|
console.log('[GAME] Stoping updates');
|
||||||
|
clearInterval(this.updateInterval);
|
||||||
|
this.computeUpdates = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
respawn(connection) {
|
||||||
|
console.log('[GAME] Player ' + connection.name + ' respawned');
|
||||||
|
this.players.get(connection.id)?.reset();
|
||||||
|
connection.send(JSON.stringify({
|
||||||
|
type: 'gamePlayerSpawn',
|
||||||
|
player: this.players.get(connection.id)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
update(connection, player) {
|
||||||
|
const playerToUpdate = this.players.get(connection.id);
|
||||||
|
if (playerToUpdate)
|
||||||
|
playerToUpdate.angle = player.angle;
|
||||||
|
}
|
||||||
|
kill(player) {
|
||||||
|
console.log('[GAME] Player ' + player.name + ' died');
|
||||||
|
player.kill();
|
||||||
|
this.sockets.get(player.id)?.send(JSON.stringify({
|
||||||
|
type: 'gamePlayerDead',
|
||||||
|
player: player
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
step() {
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const durationSinceLastUpdate = (currentTime - this.lastUpdateTime) / 1000;
|
||||||
|
const tickToSimulate = (durationSinceLastUpdate * 60) / 1000;
|
||||||
|
this.lastUpdateTime = currentTime;
|
||||||
|
// console.log('UPDATE ' + currentTime + ' doUpdate ' + doUpdate)
|
||||||
|
this.players.forEach((player, id) => {
|
||||||
|
if (player.isOutOfBorders())
|
||||||
|
this.kill(player);
|
||||||
|
this.players.forEach((player2, id2) => {
|
||||||
|
if (player.state === 'DEAD')
|
||||||
|
return;
|
||||||
|
for (let i = 0; i < player2.walls.length - 2; i++) {
|
||||||
|
// Prevent self destroy on last wall
|
||||||
|
if (player === player2 && i >= player2.walls.length - 1)
|
||||||
|
break;
|
||||||
|
const wallA = player2.walls[i];
|
||||||
|
const wallB = player2.walls[i + 1];
|
||||||
|
if (player.isCloseToWall(wallA, wallB)) {
|
||||||
|
if (player.isCrossingLine(wallA, wallB)) {
|
||||||
|
if (player !== player2)
|
||||||
|
player2.score += 300;
|
||||||
|
this.kill(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.players.forEach((player, id) => {
|
||||||
|
player.step(tickToSimulate);
|
||||||
|
});
|
||||||
|
if (this.doUpdate)
|
||||||
|
this.broadcastUpdate();
|
||||||
|
this.doUpdate = !this.doUpdate;
|
||||||
|
}
|
||||||
|
broadcastUpdate() {
|
||||||
|
const update = {
|
||||||
|
type: 'gameUpdate',
|
||||||
|
players: [...this.players.values()],
|
||||||
|
time: this.lastUpdateTime
|
||||||
|
};
|
||||||
|
this.sockets.forEach((connection, id) => {
|
||||||
|
connection.send(JSON.stringify(update));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Game = Game;
|
||||||
|
//# sourceMappingURL=Game.js.map
|
||||||
1
server/dist/Game.js.map
vendored
Normal file
1
server/dist/Game.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"Game.js","sourceRoot":"","sources":["../src/Game.ts"],"names":[],"mappings":"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"}
|
||||||
40
server/dist/app.js
vendored
Normal file
40
server/dist/app.js
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"use strict";
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const express_1 = __importDefault(require("express"));
|
||||||
|
const Game_1 = require("./Game");
|
||||||
|
const ws_1 = require("ws");
|
||||||
|
const app = express_1.default();
|
||||||
|
const WebSocketServer = ws_1.Server;
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
app.use(express_1.default.static('../distClient'));
|
||||||
|
const server = app.listen(port, () => {
|
||||||
|
console.log(`Tron.io running on port ${port}`);
|
||||||
|
});
|
||||||
|
const wss = new WebSocketServer({ server });
|
||||||
|
const game = new Game_1.Game();
|
||||||
|
wss.on('connection', function (connection) {
|
||||||
|
connection.on('close', () => game.logout(connection));
|
||||||
|
connection.on('message', (message) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(message);
|
||||||
|
switch (data.type) {
|
||||||
|
case 'login':
|
||||||
|
game.login(connection, data.name);
|
||||||
|
break;
|
||||||
|
case 'respawn':
|
||||||
|
game.respawn(connection);
|
||||||
|
break;
|
||||||
|
case 'update':
|
||||||
|
game.update(connection, data.player);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=app.js.map
|
||||||
1
server/dist/app.js.map
vendored
Normal file
1
server/dist/app.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":";;;;;AAAA,sDAA8B;AAC9B,iCAA8B;AAC9B,2BAAuC;AAGvC,MAAM,GAAG,GAAG,iBAAO,EAAE,CAAC;AACtB,MAAM,eAAe,GAAG,WAAM,CAAC;AAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AAEtC,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC;AAEzC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACnC,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAC;AACjD,CAAC,CAAC,CAAC;AAEH,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;AAE5C,MAAM,IAAI,GAAG,IAAI,WAAI,EAAE,CAAC;AAExB,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,UAAU,UAAqB;IAClD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,UAA2B,CAAC,CAAC,CAAC;IAEvE,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAE,OAAO,EAAG,EAAE;QACrC,IAAI;YACF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAiB,CAAC,CAAC;YAC3C,QAAQ,IAAI,CAAC,IAAI,EAAE;gBACjB,KAAK,OAAO;oBAAE,IAAI,CAAC,KAAK,CAAC,UAA2B,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;oBAAC,MAAM;gBACxE,KAAK,SAAS;oBAAE,IAAI,CAAC,OAAO,CAAC,UAA2B,CAAC,CAAC;oBAAC,MAAM;gBACjE,KAAK,QAAQ;oBAAE,IAAI,CAAC,MAAM,CAAC,UAA2B,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;oBAAC,MAAM;aAC7E;SACF;QAAC,OAAO,CAAC,EAAE;YACV,OAAM;SACP;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
||||||
15
server/dist/models/GameSettings.js
vendored
Normal file
15
server/dist/models/GameSettings.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.GameSettings = void 0;
|
||||||
|
class GameSettings {
|
||||||
|
constructor() {
|
||||||
|
this.playerSize = 10;
|
||||||
|
this.playerSpeed = 5;
|
||||||
|
this.playerTurnSpeed = 10;
|
||||||
|
this.wallSize = 8;
|
||||||
|
this.wallUpdate = 3;
|
||||||
|
this.arenaSize = 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.GameSettings = GameSettings;
|
||||||
|
//# sourceMappingURL=GameSettings.js.map
|
||||||
1
server/dist/models/GameSettings.js.map
vendored
Normal file
1
server/dist/models/GameSettings.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"GameSettings.js","sourceRoot":"","sources":["../../src/models/GameSettings.ts"],"names":[],"mappings":";;;AAAA,MAAa,YAAY;IAQvB;QAPA,eAAU,GAAG,EAAE,CAAC;QAChB,gBAAW,GAAG,CAAC,CAAC;QAChB,oBAAe,GAAG,EAAE,CAAC;QACrB,aAAQ,GAAG,CAAC,CAAC;QACb,eAAU,GAAG,CAAC,CAAC;QACf,cAAS,GAAG,IAAI,CAAC;IAED,CAAC;CAClB;AATD,oCASC"}
|
||||||
3
server/dist/models/GameWebSocket.js
vendored
Normal file
3
server/dist/models/GameWebSocket.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
//# sourceMappingURL=GameWebSocket.js.map
|
||||||
1
server/dist/models/GameWebSocket.js.map
vendored
Normal file
1
server/dist/models/GameWebSocket.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"GameWebSocket.js","sourceRoot":"","sources":["../../src/models/GameWebSocket.ts"],"names":[],"mappings":""}
|
||||||
75
server/dist/models/Player.js
vendored
Normal file
75
server/dist/models/Player.js
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.Player = void 0;
|
||||||
|
const Wall_1 = require("./Wall");
|
||||||
|
const GameSettings_1 = require("./GameSettings");
|
||||||
|
class Player {
|
||||||
|
constructor(id, name) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.gameSettings = new GameSettings_1.GameSettings();
|
||||||
|
this.bestScore = 0;
|
||||||
|
this.angle = 0;
|
||||||
|
this.score = 0;
|
||||||
|
this.color = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);
|
||||||
|
this.x = this.gameSettings.arenaSize * (0.25 + Math.random() * 0.5);
|
||||||
|
this.y = this.gameSettings.arenaSize * (0.25 + Math.random() * 0.5);
|
||||||
|
this.walls = [];
|
||||||
|
this.lastWall = 0;
|
||||||
|
this.state = 'ALIVE';
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
reset() {
|
||||||
|
this.score = 0;
|
||||||
|
this.color = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);
|
||||||
|
this.x = this.gameSettings.arenaSize * (0.25 + Math.random() * 0.5);
|
||||||
|
this.y = this.gameSettings.arenaSize * (0.25 + Math.random() * 0.5);
|
||||||
|
this.walls = [];
|
||||||
|
this.lastWall = 0;
|
||||||
|
this.state = 'ALIVE';
|
||||||
|
}
|
||||||
|
kill() {
|
||||||
|
this.state = 'DEAD';
|
||||||
|
if (this.bestScore < this.score)
|
||||||
|
this.bestScore = this.score;
|
||||||
|
}
|
||||||
|
isOutOfBorders() {
|
||||||
|
return this.x - this.gameSettings.playerSize < 0 ||
|
||||||
|
this.x + this.gameSettings.playerSize > this.gameSettings.arenaSize ||
|
||||||
|
this.y - this.gameSettings.playerSize < 0 ||
|
||||||
|
this.y + this.gameSettings.playerSize > this.gameSettings.arenaSize;
|
||||||
|
}
|
||||||
|
isCloseToWall(wallA, wallB) {
|
||||||
|
const xar = Math.min(wallA.x, wallB.x) - this.gameSettings.playerSize;
|
||||||
|
const yar = Math.min(wallA.y, wallB.y) - this.gameSettings.playerSize;
|
||||||
|
const xbr = Math.min(wallA.x, wallB.x) + this.gameSettings.playerSize;
|
||||||
|
const ybr = Math.min(wallA.y, wallB.y) + this.gameSettings.playerSize;
|
||||||
|
return ((this.x >= xar && this.x <= xbr) && (this.y >= yar && this.y <= ybr));
|
||||||
|
}
|
||||||
|
isCrossingLine(wallA, wallB) {
|
||||||
|
const xa = wallA.x;
|
||||||
|
const ya = wallA.y;
|
||||||
|
const xb = wallB.x;
|
||||||
|
const yb = wallB.y;
|
||||||
|
const xc = this.x;
|
||||||
|
const yc = this.y;
|
||||||
|
const radius = this.gameSettings.playerSize;
|
||||||
|
return Math.abs((yb - ya) * xc - (xb - xa) * yc + xb * ya - yb * xa) / Math.sqrt(Math.pow(xb - xa, 2) + Math.pow(yb - ya, 2)) < radius;
|
||||||
|
}
|
||||||
|
step(tickToSimulate) {
|
||||||
|
if (this.state === 'DEAD')
|
||||||
|
return;
|
||||||
|
for (let i = 0; i < tickToSimulate; i++) {
|
||||||
|
this.lastWall++;
|
||||||
|
if (this.lastWall > this.gameSettings.wallUpdate) {
|
||||||
|
this.walls.push(new Wall_1.Wall(this.x, this.y));
|
||||||
|
this.score++;
|
||||||
|
this.lastWall = 0;
|
||||||
|
}
|
||||||
|
this.x = this.x + this.gameSettings.playerSpeed * Math.cos(this.angle);
|
||||||
|
this.y = this.y + this.gameSettings.playerSpeed * Math.sin(this.angle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Player = Player;
|
||||||
|
//# sourceMappingURL=Player.js.map
|
||||||
1
server/dist/models/Player.js.map
vendored
Normal file
1
server/dist/models/Player.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"Player.js","sourceRoot":"","sources":["../../src/models/Player.ts"],"names":[],"mappings":"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"}
|
||||||
11
server/dist/models/Wall.js
vendored
Normal file
11
server/dist/models/Wall.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.Wall = void 0;
|
||||||
|
class Wall {
|
||||||
|
constructor(x, y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Wall = Wall;
|
||||||
|
//# sourceMappingURL=Wall.js.map
|
||||||
1
server/dist/models/Wall.js.map
vendored
Normal file
1
server/dist/models/Wall.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"Wall.js","sourceRoot":"","sources":["../../src/models/Wall.ts"],"names":[],"mappings":";;;AAAA,MAAa,IAAI;IACf,YAAoB,CAAS,EAAS,CAAS;QAA3B,MAAC,GAAD,CAAC,CAAQ;QAAS,MAAC,GAAD,CAAC,CAAQ;IAAI,CAAC;CACrD;AAFD,oBAEC"}
|
||||||
1627
server/package-lock.json
generated
1627
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "tronio-server",
|
"name": "tronio-server",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"description": "Tron.io game server",
|
"description": "Tron.io game server",
|
||||||
"main": "server.js",
|
"main": "dist/app.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/server.js",
|
"start": "tsc && node dist/app.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"build": "tsc",
|
||||||
|
"lint": "echo \"Warning: no lint specified\"",
|
||||||
|
"test": "echo \"Warning: no test specified\""
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -14,15 +16,16 @@
|
|||||||
"author": "gltron",
|
"author": "gltron",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"uuid": "^8.3.1",
|
"uuid": "^8.3.1",
|
||||||
"ws": "^7.3.1"
|
"ws": "^7.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^7.12.1",
|
"@types/express": "^4.17.11",
|
||||||
"eslint-config-standard": "^16.0.0",
|
"@types/node": "^14.14.22",
|
||||||
"eslint-plugin-import": "^2.22.1",
|
"@types/uuid": "^8.3.0",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"@types/ws": "^7.4.0",
|
||||||
"eslint-plugin-promise": "^4.2.1"
|
"typescript": "^4.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
133
server/src/Game.ts
Normal file
133
server/src/Game.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
import { Player } from './models/Player';
|
||||||
|
import { GameSettings } from './models/GameSettings';
|
||||||
|
import { GameWebSocket } from './models/GameWebSocket';
|
||||||
|
|
||||||
|
export class Game {
|
||||||
|
private gameSettings = new GameSettings();
|
||||||
|
private players = new Map<string, Player>();
|
||||||
|
private sockets = new Map<string, WebSocket>();
|
||||||
|
|
||||||
|
private computeUpdates = false;
|
||||||
|
private updateInterval = setInterval(() => this.step(), 10000000);
|
||||||
|
private lastUpdateTime = Date.now();
|
||||||
|
private doUpdate = true;
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
clearInterval(this.updateInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
public login (connection: GameWebSocket, name: string): void {
|
||||||
|
console.log('[GAME] Player ' + name + ' connected | ' + this.players.size);
|
||||||
|
const id = uuidv4();
|
||||||
|
connection.id = id;
|
||||||
|
connection.name = name;
|
||||||
|
this.sockets.set(id, connection);
|
||||||
|
this.players.set(id, new Player(id, name));
|
||||||
|
|
||||||
|
connection.send(JSON.stringify({
|
||||||
|
type: 'login',
|
||||||
|
player: this.players.get(id)
|
||||||
|
}));
|
||||||
|
|
||||||
|
connection.send(JSON.stringify({
|
||||||
|
type: 'gameSettings',
|
||||||
|
gameSettings: this.gameSettings
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!this.computeUpdates) {
|
||||||
|
console.log('[GAME] Starting updates');
|
||||||
|
this.updateInterval = setInterval(() => this.step(), 1000 / 60);
|
||||||
|
this.computeUpdates = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public logout (connection: GameWebSocket): void {
|
||||||
|
console.log('[GAME] Player ' + connection.name + ' disconnected | ' + this.players.size);
|
||||||
|
this.sockets.delete(connection.id);
|
||||||
|
this.players.delete(connection.id);
|
||||||
|
|
||||||
|
if (this.players.size === 0) {
|
||||||
|
console.log('[GAME] Stoping updates');
|
||||||
|
clearInterval(this.updateInterval);
|
||||||
|
this.computeUpdates = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public respawn (connection: GameWebSocket): void {
|
||||||
|
console.log('[GAME] Player ' + connection.name + ' respawned');
|
||||||
|
this.players.get(connection.id)?.reset();
|
||||||
|
|
||||||
|
connection.send(JSON.stringify({
|
||||||
|
type: 'gamePlayerSpawn',
|
||||||
|
player: this.players.get(connection.id)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
public update (connection: GameWebSocket, player: Player): void {
|
||||||
|
const playerToUpdate = this.players.get(connection.id);
|
||||||
|
if (playerToUpdate) playerToUpdate.angle = player.angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public kill (player: Player): void {
|
||||||
|
console.log('[GAME] Player ' + player.name + ' died');
|
||||||
|
player.kill();
|
||||||
|
this.sockets.get(player.id)?.send(JSON.stringify({
|
||||||
|
type: 'gamePlayerDead',
|
||||||
|
player: player
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
private step (): void {
|
||||||
|
const currentTime = Date.now()
|
||||||
|
const durationSinceLastUpdate = (currentTime - this.lastUpdateTime) / 1000;
|
||||||
|
const tickToSimulate = (durationSinceLastUpdate * 60) / 1000;
|
||||||
|
this.lastUpdateTime = currentTime;
|
||||||
|
|
||||||
|
// console.log('UPDATE ' + currentTime + ' doUpdate ' + doUpdate)
|
||||||
|
|
||||||
|
this.players.forEach((player, id) => {
|
||||||
|
if (player.isOutOfBorders()) this.kill(player);
|
||||||
|
|
||||||
|
this.players.forEach((player2, id2) => {
|
||||||
|
if (player.state === 'DEAD') return
|
||||||
|
|
||||||
|
for (let i = 0; i < player2.walls.length - 2; i++) {
|
||||||
|
// Prevent self destroy on last wall
|
||||||
|
if (player === player2 && i >= player2.walls.length - 1) break
|
||||||
|
|
||||||
|
const wallA = player2.walls[i]
|
||||||
|
const wallB = player2.walls[i + 1]
|
||||||
|
|
||||||
|
if (player.isCloseToWall(wallA, wallB)) {
|
||||||
|
if (player.isCrossingLine(wallA, wallB)) {
|
||||||
|
if (player !== player2) player2.score += 300
|
||||||
|
this.kill(player);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.players.forEach((player, id) => {
|
||||||
|
player.step(tickToSimulate);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.doUpdate) this.broadcastUpdate();
|
||||||
|
this.doUpdate = !this.doUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private broadcastUpdate () {
|
||||||
|
const update = {
|
||||||
|
type: 'gameUpdate',
|
||||||
|
players: [ ...this.players.values() ],
|
||||||
|
time: this.lastUpdateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sockets.forEach((connection, id) => {
|
||||||
|
connection.send(JSON.stringify(update));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
35
server/src/app.ts
Normal file
35
server/src/app.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import { Game } from './Game';
|
||||||
|
import WebSocket, { Server } from 'ws';
|
||||||
|
import { GameWebSocket } from './models/GameWebSocket';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const WebSocketServer = Server;
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
app.use(express.static('../distClient'));
|
||||||
|
|
||||||
|
const server = app.listen(port, () => {
|
||||||
|
console.log(`Tron.io running on port ${port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({ server });
|
||||||
|
|
||||||
|
const game = new Game();
|
||||||
|
|
||||||
|
wss.on('connection', function (connection: WebSocket) {
|
||||||
|
connection.on('close', () => game.logout(connection as GameWebSocket));
|
||||||
|
|
||||||
|
connection.on('message', ( message ) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(message as string);
|
||||||
|
switch (data.type) {
|
||||||
|
case 'login': game.login(connection as GameWebSocket, data.name); break;
|
||||||
|
case 'respawn': game.respawn(connection as GameWebSocket); break;
|
||||||
|
case 'update': game.update(connection as GameWebSocket, data.player); break;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
const { v4: uuidv4 } = require('uuid')
|
|
||||||
const Player = require('./models/player')
|
|
||||||
const gameSettings = require('./models/gameSettings')
|
|
||||||
|
|
||||||
const players = {}
|
|
||||||
const sockets = {}
|
|
||||||
|
|
||||||
let updateInterval = -1
|
|
||||||
|
|
||||||
let lastUpdateTime = Date.now()
|
|
||||||
let doUpdate = true
|
|
||||||
|
|
||||||
function login (connection, name) {
|
|
||||||
const id = uuidv4()
|
|
||||||
connection.id = id
|
|
||||||
sockets[id] = connection
|
|
||||||
players[id] = new Player(id, name)
|
|
||||||
|
|
||||||
connection.send(JSON.stringify({
|
|
||||||
type: 'login',
|
|
||||||
player: players[id]
|
|
||||||
}))
|
|
||||||
|
|
||||||
connection.send(JSON.stringify({
|
|
||||||
type: 'gameSettings',
|
|
||||||
gameSettings: gameSettings
|
|
||||||
}))
|
|
||||||
|
|
||||||
if (updateInterval === -1) updateInterval = setInterval(() => step(), 1000 / 60)
|
|
||||||
}
|
|
||||||
|
|
||||||
function logout (connection) {
|
|
||||||
delete sockets[connection.id]
|
|
||||||
delete players[connection.id]
|
|
||||||
|
|
||||||
if (Object.keys(players).length === 0) {
|
|
||||||
clearInterval(updateInterval)
|
|
||||||
updateInterval = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function respawn (connection) {
|
|
||||||
players[connection.id].reset()
|
|
||||||
|
|
||||||
connection.send(JSON.stringify({
|
|
||||||
type: 'gamePlayerSpawn',
|
|
||||||
player: players[connection.id]
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
function update (connection, player) {
|
|
||||||
players[connection.id].angle = player.angle
|
|
||||||
}
|
|
||||||
|
|
||||||
function kill (player) {
|
|
||||||
player.kill()
|
|
||||||
sockets[player.id].send(JSON.stringify({
|
|
||||||
type: 'gamePlayerDead',
|
|
||||||
player: player
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
function step () {
|
|
||||||
const currentTime = Date.now()
|
|
||||||
const durationSinceLastUpdate = (currentTime - lastUpdateTime) / 1000
|
|
||||||
const tickToSimulate = (durationSinceLastUpdate * 60) / 1000
|
|
||||||
lastUpdateTime = currentTime
|
|
||||||
|
|
||||||
// console.log('UPDATE ' + currentTime + ' doUpdate ' + doUpdate)
|
|
||||||
|
|
||||||
Object.values(players).forEach((player) => {
|
|
||||||
if (player.isOutOfBorders()) kill(player)
|
|
||||||
|
|
||||||
Object.values(players).forEach((player2) => {
|
|
||||||
if (player.state === 'DEAD') return
|
|
||||||
|
|
||||||
for (let i = 0; i < player2.walls.length - 2; i++) {
|
|
||||||
// Prevent self destroy on last wall
|
|
||||||
if (player === player2 && i >= player2.walls.length - 1) break
|
|
||||||
|
|
||||||
const wallA = player2.walls[i]
|
|
||||||
const wallB = player2.walls[i + 1]
|
|
||||||
|
|
||||||
if (player.isCloseToWall(wallA, wallB)) {
|
|
||||||
if (player.isCrossingLine(wallA, wallB)) {
|
|
||||||
if (player !== player2) player2.score += 300
|
|
||||||
kill(player)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Object.values(players).forEach((player) => {
|
|
||||||
player.step(tickToSimulate)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (doUpdate) broadcastUpdate()
|
|
||||||
doUpdate = !doUpdate
|
|
||||||
}
|
|
||||||
|
|
||||||
function broadcastUpdate () {
|
|
||||||
const update = {
|
|
||||||
type: 'gameUpdate',
|
|
||||||
players: Object.values(players),
|
|
||||||
time: lastUpdateTime
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.values(sockets).forEach((connection) => {
|
|
||||||
connection.send(JSON.stringify(update))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
login,
|
|
||||||
logout,
|
|
||||||
respawn,
|
|
||||||
update
|
|
||||||
}
|
|
||||||
10
server/src/models/GameSettings.ts
Normal file
10
server/src/models/GameSettings.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export class GameSettings {
|
||||||
|
playerSize = 10;
|
||||||
|
playerSpeed = 5;
|
||||||
|
playerTurnSpeed = 10;
|
||||||
|
wallSize = 8;
|
||||||
|
wallUpdate = 3;
|
||||||
|
arenaSize = 1000;
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
}
|
||||||
6
server/src/models/GameWebSocket.ts
Normal file
6
server/src/models/GameWebSocket.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
|
export interface GameWebSocket extends WebSocket {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
80
server/src/models/Player.ts
Normal file
80
server/src/models/Player.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { Wall } from './Wall';
|
||||||
|
import { GameSettings } from './GameSettings';
|
||||||
|
|
||||||
|
export class Player {
|
||||||
|
|
||||||
|
private gameSettings = new GameSettings();
|
||||||
|
public bestScore = 0;
|
||||||
|
public angle = 0;
|
||||||
|
public score = 0;
|
||||||
|
public color = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);
|
||||||
|
public x = this.gameSettings.arenaSize * (0.25 + Math.random() * 0.5);
|
||||||
|
public y = this.gameSettings.arenaSize * (0.25 + Math.random() * 0.5);
|
||||||
|
public walls: Wall[] = [];
|
||||||
|
public lastWall = 0;
|
||||||
|
public state = 'ALIVE';
|
||||||
|
|
||||||
|
constructor (public id: string, public name: string) {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset () {
|
||||||
|
this.score = 0;
|
||||||
|
this.color = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);
|
||||||
|
this.x = this.gameSettings.arenaSize * (0.25 + Math.random() * 0.5);
|
||||||
|
this.y = this.gameSettings.arenaSize * (0.25 + Math.random() * 0.5);
|
||||||
|
this.walls = [];
|
||||||
|
this.lastWall = 0;
|
||||||
|
this.state = 'ALIVE';
|
||||||
|
}
|
||||||
|
|
||||||
|
kill () {
|
||||||
|
this.state = 'DEAD';
|
||||||
|
if (this.bestScore < this.score) this.bestScore = this.score;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOutOfBorders () {
|
||||||
|
return this.x - this.gameSettings.playerSize < 0 ||
|
||||||
|
this.x + this.gameSettings.playerSize > this.gameSettings.arenaSize ||
|
||||||
|
this.y - this.gameSettings.playerSize < 0 ||
|
||||||
|
this.y + this.gameSettings.playerSize > this.gameSettings.arenaSize
|
||||||
|
}
|
||||||
|
|
||||||
|
isCloseToWall (wallA: Wall, wallB: Wall) {
|
||||||
|
const xar = Math.min(wallA.x, wallB.x) - this.gameSettings.playerSize
|
||||||
|
const yar = Math.min(wallA.y, wallB.y) - this.gameSettings.playerSize
|
||||||
|
const xbr = Math.min(wallA.x, wallB.x) + this.gameSettings.playerSize
|
||||||
|
const ybr = Math.min(wallA.y, wallB.y) + this.gameSettings.playerSize
|
||||||
|
|
||||||
|
return ((this.x >= xar && this.x <= xbr) && (this.y >= yar && this.y <= ybr))
|
||||||
|
}
|
||||||
|
|
||||||
|
isCrossingLine (wallA: Wall, wallB: Wall) {
|
||||||
|
const xa = wallA.x
|
||||||
|
const ya = wallA.y
|
||||||
|
const xb = wallB.x
|
||||||
|
const yb = wallB.y
|
||||||
|
const xc = this.x
|
||||||
|
const yc = this.y
|
||||||
|
const radius = this.gameSettings.playerSize
|
||||||
|
|
||||||
|
return Math.abs((yb - ya) * xc - (xb - xa) * yc + xb * ya - yb * xa) / Math.sqrt(Math.pow(xb - xa, 2) + Math.pow(yb - ya, 2)) < radius
|
||||||
|
}
|
||||||
|
|
||||||
|
step (tickToSimulate: number) {
|
||||||
|
if (this.state === 'DEAD') return
|
||||||
|
|
||||||
|
for (let i = 0; i < tickToSimulate; i++) {
|
||||||
|
this.lastWall++
|
||||||
|
|
||||||
|
if (this.lastWall > this.gameSettings.wallUpdate) {
|
||||||
|
this.walls.push(new Wall(this.x, this.y))
|
||||||
|
this.score++
|
||||||
|
this.lastWall = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
this.x = this.x + this.gameSettings.playerSpeed * Math.cos(this.angle)
|
||||||
|
this.y = this.y + this.gameSettings.playerSpeed * Math.sin(this.angle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
server/src/models/Wall.ts
Normal file
3
server/src/models/Wall.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export class Wall {
|
||||||
|
constructor (public x: number, public y: number) { }
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
module.exports = Object.freeze({
|
|
||||||
playerSize: 10,
|
|
||||||
playerSpeed: 5,
|
|
||||||
playerTurnSpeed: 10,
|
|
||||||
wallSize: 8,
|
|
||||||
wallUpdate: 5,
|
|
||||||
arenaSize: 1000
|
|
||||||
})
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
const Wall = require('./wall')
|
|
||||||
const gameSettings = require('./gameSettings')
|
|
||||||
|
|
||||||
class Player {
|
|
||||||
constructor (id, name) {
|
|
||||||
this.id = id
|
|
||||||
this.name = name
|
|
||||||
this.bestScore = 0
|
|
||||||
this.angle = 0
|
|
||||||
this.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
reset () {
|
|
||||||
this.score = 0
|
|
||||||
this.color = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6)
|
|
||||||
this.x = gameSettings.arenaSize * (0.25 + Math.random() * 0.5)
|
|
||||||
this.y = gameSettings.arenaSize * (0.25 + Math.random() * 0.5)
|
|
||||||
this.walls = []
|
|
||||||
this.lastWall = 0
|
|
||||||
this.state = 'ALIVE'
|
|
||||||
}
|
|
||||||
|
|
||||||
kill () {
|
|
||||||
this.state = 'DEAD'
|
|
||||||
if (this.bestScore < this.score) this.bestScore = this.score
|
|
||||||
}
|
|
||||||
|
|
||||||
isOutOfBorders () {
|
|
||||||
return this.x - gameSettings.playerSize < 0 ||
|
|
||||||
this.x + gameSettings.playerSize > gameSettings.arenaSize ||
|
|
||||||
this.y - gameSettings.playerSize < 0 ||
|
|
||||||
this.y + gameSettings.playerSize > gameSettings.arenaSize
|
|
||||||
}
|
|
||||||
|
|
||||||
isCloseToWall (wallA, wallB) {
|
|
||||||
const xar = Math.min(wallA.x, wallB.x) - gameSettings.playerSize
|
|
||||||
const yar = Math.min(wallA.y, wallB.y) - gameSettings.playerSize
|
|
||||||
const xbr = Math.min(wallA.x, wallB.x) + gameSettings.playerSize
|
|
||||||
const ybr = Math.min(wallA.y, wallB.y) + gameSettings.playerSize
|
|
||||||
|
|
||||||
return ((this.x >= xar && this.x <= xbr) && (this.y >= yar && this.y <= ybr))
|
|
||||||
}
|
|
||||||
|
|
||||||
isCrossingLine (wallA, wallB) {
|
|
||||||
const xa = wallA.x
|
|
||||||
const ya = wallA.y
|
|
||||||
const xb = wallB.x
|
|
||||||
const yb = wallB.y
|
|
||||||
const xc = this.x
|
|
||||||
const yc = this.y
|
|
||||||
const radius = gameSettings.playerSize
|
|
||||||
|
|
||||||
return Math.abs((yb - ya) * xc - (xb - xa) * yc + xb * ya - yb * xa) / Math.sqrt(Math.pow(xb - xa, 2) + Math.pow(yb - ya, 2)) < radius
|
|
||||||
}
|
|
||||||
|
|
||||||
step (tickToSimulate) {
|
|
||||||
if (this.state === 'DEAD') return
|
|
||||||
|
|
||||||
for (let i = 0; i < tickToSimulate; i++) {
|
|
||||||
this.lastWall++
|
|
||||||
|
|
||||||
if (this.lastWall > gameSettings.wallUpdate) {
|
|
||||||
this.walls.push(new Wall(this.x, this.y))
|
|
||||||
this.score++
|
|
||||||
this.lastWall = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
this.x = this.x + gameSettings.playerSpeed * Math.cos(this.angle)
|
|
||||||
this.y = this.y + gameSettings.playerSpeed * Math.sin(this.angle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Player
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
class Wall {
|
|
||||||
constructor (x, y) {
|
|
||||||
this.x = x
|
|
||||||
this.y = y
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Wall
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
const express = require('express')
|
|
||||||
const app = express()
|
|
||||||
const game = require('./game')
|
|
||||||
const WebSocketServer = require('ws').Server
|
|
||||||
const port = process.env.PORT || 3000
|
|
||||||
|
|
||||||
app.use(express.static('dist'))
|
|
||||||
|
|
||||||
const server = app.listen(port, () => {
|
|
||||||
console.log(`Tron.io running on port ${port}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
const wss = new WebSocketServer({ server })
|
|
||||||
|
|
||||||
wss.on('connection', function (connection) {
|
|
||||||
connection.on('close', () => game.logout(connection))
|
|
||||||
|
|
||||||
connection.on('message', (message) => {
|
|
||||||
let data
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = JSON.parse(message)
|
|
||||||
} catch (e) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (data.type) {
|
|
||||||
case 'login': game.login(connection, data.name); break
|
|
||||||
case 'respawn': game.respawn(connection); break
|
|
||||||
case 'update': game.update(connection, data.player); break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
11
server/tsconfig.json
Normal file
11
server/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"target": "ES2020",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user