26 Commits

Author SHA1 Message Date
893ffe4aa4 make it possible for ai to make the first move 2022-07-29 05:53:52 +00:00
f71602f385 learn from multiple games with fresh model each 2022-07-29 05:35:58 +00:00
OezmenK
539b3c02f5 finished Tic-Tac-Toe Project 2022-07-26 15:29:57 +02:00
3ab800f967 renable modern tfjs 2022-07-19 19:24:38 +00:00
69223a513f fix allmoves to contain all flipped moves 2022-07-19 19:21:36 +00:00
80e67f41d7 fix flipY method 2022-07-19 19:21:20 +00:00
c2aa53ac88 enable ai training 2022-07-19 19:02:27 +00:00
21ffed5566 revert to tfjs 1.7.4 and fix compile-bug 2022-07-19 19:02:09 +00:00
1197fcbcad add more eslint ignores, for now. fix this! 2022-07-19 19:01:44 +00:00
OezmenK
9e542c64ba added train buttons 2022-07-19 14:54:29 +02:00
OezmenK
907284b098 Merge branch 'development' of https://gitea.tahir-it.de/david.tahir/AngularTicTacToe into development 2022-07-19 14:52:57 +02:00
OezmenK
877f6cd529 added bug fix for tf 2022-07-19 14:48:38 +02:00
unknown
13fe8d511e added @types/long to packages and edited compilerOptions 2022-07-19 14:44:37 +02:00
6eb2d78f8c disable es-lint for now 2022-07-19 12:33:22 +00:00
OezmenK
6e97d11f43 added history 2022-07-19 13:40:44 +02:00
OezmenK
0226cb9cfb fresh package pull 2022-07-12 14:02:42 +02:00
OezmenK
ae1cd8944c Merge branch 'development' of https://gitea.tahir-it.de/david.tahir/AngularTicTacToe into development 2022-07-12 13:40:47 +02:00
OezmenK
88e25143bf added ai.service.ts file 2022-06-29 07:48:24 +02:00
OezmenK
a34dec4f43 added tensorflow/tfjs 2022-06-29 07:48:00 +02:00
David Tahir
f51f89e3b0 added devcontainers 2022-06-28 12:27:49 +00:00
OezmenK
2a47eb6b20 deleted translate 2022-06-28 13:46:32 +02:00
OezmenK
85267bfe2c changed winning conditions 2022-06-28 13:45:54 +02:00
OezmenK
62bf421c9b adde top down vertical test 2022-06-28 13:45:14 +02:00
OezmenK
fd9be97387 changed browser window size 2022-06-28 13:44:43 +02:00
OezmenK
bc829346ae added nebular theme 2022-06-28 13:43:54 +02:00
Oezmen
2e0c68a636 changes 2022-06-14 14:27:32 +02:00
20 changed files with 3432 additions and 1907 deletions

14
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
ARG VARIANT=16-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment if you want to install an additional version of node using nvm
# ARG EXTRA_NODE_VERSION=10
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
# [Optional] Uncomment if you want to install more global node modules
# RUN su node -c "npm install -g <your-package-list-here>"

View File

@@ -0,0 +1,55 @@
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
ARG VARIANT=16-bullseye
FROM node:${VARIANT}
# [Option] Install zsh
ARG INSTALL_ZSH="true"
# [Option] Upgrade OS packages to their latest versions
ARG UPGRADE_PACKAGES="true"
# Install needed packages, yarn, nvm and setup non-root user. Use a separate RUN statement to add your own dependencies.
ARG USERNAME=node
ARG USER_UID=1000
ARG USER_GID=$USER_UID
ARG NPM_GLOBAL=/usr/local/share/npm-global
ENV NVM_DIR=/usr/local/share/nvm
ENV NVM_SYMLINK_CURRENT=true \
PATH=${NPM_GLOBAL}/bin:${NVM_DIR}/current/bin:${PATH}
COPY library-scripts/*.sh library-scripts/*.env /tmp/library-scripts/
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# Remove imagemagick due to https://security-tracker.debian.org/tracker/CVE-2019-10131
&& apt-get purge -y imagemagick imagemagick-6-common \
# Install common packages, non-root user, update yarn and install nvm
&& bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true" \
# Install yarn, nvm
&& rm -rf /opt/yarn-* /usr/local/bin/yarn /usr/local/bin/yarnpkg \
&& bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" "none" "${USERNAME}" \
# Configure global npm install location, use group to adapt to UID/GID changes
&& if ! cat /etc/group | grep -e "^npm:" > /dev/null 2>&1; then groupadd -r npm; fi \
&& usermod -a -G npm ${USERNAME} \
&& umask 0002 \
&& mkdir -p ${NPM_GLOBAL} \
&& touch /usr/local/etc/npmrc \
&& chown ${USERNAME}:npm ${NPM_GLOBAL} /usr/local/etc/npmrc \
&& chmod g+s ${NPM_GLOBAL} \
&& npm config -g set prefix ${NPM_GLOBAL} \
&& sudo -u ${USERNAME} npm config -g set prefix ${NPM_GLOBAL} \
# Install eslint
&& su ${USERNAME} -c "umask 0002 && npm install -g eslint" \
&& npm cache clean --force > /dev/null 2>&1 \
# Install python-is-python3 on bullseye to prevent node-gyp regressions
&& . /etc/os-release \
&& if [ "${VERSION_CODENAME}" = "bullseye" ]; then apt-get -y install --no-install-recommends python-is-python3; fi \
# Clean up
&& apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /root/.gnupg /tmp/library-scripts
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment if you want to install an additional version of node using nvm
# ARG EXTRA_NODE_VERSION=10
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
# [Optional] Uncomment if you want to install more global node modules
# RUN su node -c "npm install -g <your-package-list-here>""

View File

@@ -0,0 +1,32 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/javascript-node
{
"name": "Node.js",
"build": {
"dockerfile": "Dockerfile",
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local arm64/Apple Silicon.
"args": { "VARIANT": "16-bullseye" }
},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"dbaeumer.vscode-eslint"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "node"
}

View File

@@ -16,8 +16,8 @@ function createWindow(): BrowserWindow {
win = new BrowserWindow({ win = new BrowserWindow({
x: 0, x: 0,
y: 0, y: 0,
width: size.width, width: 650,
height: size.height, height: 700,
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
allowRunningInsecureContent: (serve) ? true : false, allowRunningInsecureContent: (serve) ? true : false,

4889
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -45,14 +45,20 @@
"lint": "ng lint" "lint": "ng lint"
}, },
"dependencies": { "dependencies": {
"@angular/common": "13.3.1", "@angular/animations": "13.3.11",
"@angular/compiler": "13.3.1", "@angular/cdk": "^13.0.0",
"@angular/core": "13.3.1", "@angular/common": "13.3.11",
"@angular/forms": "13.3.1", "@angular/compiler": "13.3.11",
"@angular/language-service": "13.3.1", "@angular/core": "13.3.11",
"@angular/platform-browser": "13.3.1", "@angular/forms": "13.3.11",
"@angular/platform-browser-dynamic": "13.3.1", "@angular/language-service": "13.3.11",
"@angular/router": "13.3.1", "@angular/platform-browser": "13.3.11",
"@angular/platform-browser-dynamic": "13.3.11",
"@angular/router": "13.3.11",
"@nebular/eva-icons": "9.0.3",
"@nebular/theme": "9.0.3",
"@tensorflow/tfjs": "1.7.4",
"eva-icons": "^1.1.2",
"rxjs": "7.5.5", "rxjs": "7.5.5",
"tslib": "^2.1.0", "tslib": "^2.1.0",
"zone.js": "~0.11.5" "zone.js": "~0.11.5"
@@ -66,12 +72,14 @@
"@angular-eslint/schematics": "13.1.0", "@angular-eslint/schematics": "13.1.0",
"@angular-eslint/template-parser": "13.1.0", "@angular-eslint/template-parser": "13.1.0",
"@angular/cli": "13.3.1", "@angular/cli": "13.3.1",
"@angular/compiler-cli": "13.3.1", "@angular/compiler-cli": "13.3.11",
"@ngx-translate/core": "14.0.0", "@ngx-translate/core": "14.0.0",
"@ngx-translate/http-loader": "7.0.0", "@ngx-translate/http-loader": "7.0.0",
"@playwright/test": "1.20.2", "@playwright/test": "1.20.2",
"@schematics/angular": "13.3.1",
"@types/jasmine": "3.10.3", "@types/jasmine": "3.10.3",
"@types/jasminewd2": "2.0.10", "@types/jasminewd2": "2.0.10",
"@types/long": "4.0.2",
"@types/node": "17.0.23", "@types/node": "17.0.23",
"@typescript-eslint/eslint-plugin": "5.17.0", "@typescript-eslint/eslint-plugin": "5.17.0",
"@typescript-eslint/parser": "5.17.0", "@typescript-eslint/parser": "5.17.0",

164
src/app/ai.service.ts Normal file
View File

@@ -0,0 +1,164 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/no-unused-expressions */
/* eslint-disable space-before-function-paren */
/* eslint-disable prefer-const */
/* eslint-disable @typescript-eslint/quotes */
/* eslint-disable @typescript-eslint/member-ordering */
import * as tf from "@tensorflow/tfjs";
export class AiService {
constructor() {
}
async doPredict(myBoard) {
const tenseBlock = tf.tensor([myBoard]);
const result = await this.currentModel.predict(tenseBlock);
const flatty = result.flatten();
const maxy = flatty.argMax();
const move = await maxy.data();
const allMoves = await flatty.data();
flatty.dispose();
tenseBlock.dispose();
result.dispose();
maxy.dispose();
return [move[0], allMoves];
};
private currentModel;
flipX(arr) {
return [arr.slice(6), arr.slice(3, 6), arr.slice(0, 3)].flat();
};
flipY(arr) {
return this.flipX(arr.slice().reverse());
}
// Creates a 1 hot of the diff
showMove(first, second) {
let result = [];
first.forEach((move, i) => {
result.push(Math.abs(move - second[i]));
});
return result;
};
getMoves (block) {
let x = [];
let y = [];
// Make all the moves
for (let i = 0; i < block.length - 1; i++) {
const theMove = this.showMove(block[i], block[i + 1]);
// Normal move
x.push(block[i]);
y.push(theMove);
// Flipped X move
x.push(this.flipX(block[i]));
y.push(this.flipX(theMove));
// Inverted Move
x.push(block[i].slice().reverse());
y.push(theMove.slice().reverse());
// Flipped Y move
x.push(this.flipY(block[i]));
y.push(this.flipY(theMove));
}
return { x, y };
};
constructModel() {
this.currentModel && this.currentModel.dispose();
tf.disposeVariables();
const model = tf.sequential();
model.add(
tf.layers.dense({
inputShape: [9],
units: 64,
activation: "relu"
})
);
model.add(
tf.layers.dense({
units: 64,
activation: "relu"
})
);
model.add(
tf.layers.dense({
units: 9,
activation: "softmax"
})
);
const learningRate = 0.005;
model.compile({
optimizer: tf.train.adam(learningRate),
loss: "categoricalCrossentropy",
metrics: ["accuracy"]
});
this.currentModel = model;
return model;
};
getModel() {
if (this.currentModel) {
return this.currentModel;
} else {
return this.constructModel();
}
};
async trainOnGames(games, setState) {
const model = this.constructModel();
// model.dispose();
let AllX = [];
let AllY = [];
// console.log("Games in", JSON.stringify(games));
games.forEach((game) => {
AllX = AllX.concat(game.x);
AllY = AllY.concat(game.y);
});
// Tensorfy!
console.log(AllX);
const stackedX = tf.stack(AllX);
const stackedY = tf.stack(AllY);
await this.trainModel(model, stackedX, stackedY);
// clean up!
stackedX.dispose();
stackedY.dispose();
setState(model);
// return updatedModel;
};
async trainModel (model, stackedX, stackedY) {
const allCallbacks = {
// onTrainBegin: log => console.log(log),
// onTrainEnd: log => console.log(log),
// onEpochBegin: (epoch, log) => console.log(epoch, log),
onEpochEnd: (epoch, log) => console.log(epoch, log)
// onBatchBegin: (batch, log) => console.log(batch, log),
// onBatchEnd: (batch, log) => console.log(batch, log)
};
await model.fit(stackedX, stackedY, {
epochs: 100,
shuffle: true,
batchSize: 32,
callbacks: allCallbacks
});
console.log("Model Trained");
return model;
};
}

View File

@@ -1 +1,15 @@
<nb-layout>
<nb-layout-header fixed>
<!-- Insert header here -->
</nb-layout-header>
<nb-layout-column>
<app-board></app-board> <app-board></app-board>
</nb-layout-column>
<nb-layout-footer fixed>
<!-- Insert footer here -->
</nb-layout-footer>
</nb-layout>

View File

@@ -14,6 +14,9 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { BoardModule } from './board/board.module'; import { BoardModule } from './board/board.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NbThemeModule, NbLayoutModule } from '@nebular/theme';
import { NbEvaIconsModule } from '@nebular/eva-icons';
// AoT requires an exported function for factories // AoT requires an exported function for factories
@@ -34,7 +37,11 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl
useFactory: httpLoaderFactory, useFactory: httpLoaderFactory,
deps: [HttpClient] deps: [HttpClient]
} }
}) }),
BrowserAnimationsModule,
NbThemeModule.forRoot({ name: 'cosmic' }),
NbLayoutModule,
NbEvaIconsModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@@ -2,7 +2,10 @@
<h1>Tic-Tac-Toe</h1> <h1>Tic-Tac-Toe</h1>
</section> </section>
<section w-full> <section w-full>
<p><button (click)="resetGame()">Neues Spiel!<br /></button><br /></p> <p><button nbButton outline status="danger" (click)="resetGame()">Neues Spiel!<br /></button><br /></p>
<p><button nbButton outline status="danger" (click)="trainUp('X')" *ngIf="winner">Train like X!<br /></button><br /></p>
<p><button nbButton outline status="danger" (click)="trainUp('O')" *ngIf="winner">Train like O!<br /></button><br /></p>
<p><button nbButton outline status="success" (click)="makeAiMove()">Make Ai move<br /></button><br /></p>
<h1>current player: {{ player }}</h1> <h1>current player: {{ player }}</h1>
<h2 *ngIf="winner">Gewinner: {{ winner }}<br /></h2><br /> <h2 *ngIf="winner">Gewinner: {{ winner }}<br /></h2><br />
</section> </section>
@@ -10,3 +13,4 @@
<app-square class="square-container h-48" *ngFor="let val of fields; let i=index" [value]="val" <app-square class="square-container h-48" *ngFor="let val of fields; let i=index" [value]="val"
(click)="playerPress(i)"></app-square> (click)="playerPress(i)"></app-square>
</section> </section>

View File

@@ -40,6 +40,15 @@ describe('BoardComponent', () => {
expect(component.calculateWinner()).not.toBe(null); expect(component.calculateWinner()).not.toBe(null);
}); });
it('top down vertical test', () => {
component.playerPress(1);
component.playerPress(0);
component.playerPress(4);
component.playerPress(5);
component.playerPress(7);
expect(component.calculateWinner()).not.toBe(null);
}); });
});

View File

@@ -1,4 +1,9 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable arrow-body-style */
/* eslint-disable @typescript-eslint/prefer-for-of */
/* eslint-disable @typescript-eslint/member-ordering */
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { AiService } from '../ai.service';
@Component({ @Component({
selector: 'app-board', selector: 'app-board',
@@ -10,6 +15,10 @@ export class BoardComponent implements OnInit {
draw: boolean; draw: boolean;
xIsNext: boolean; xIsNext: boolean;
fields: any[]; fields: any[];
history: any[];
ai = new AiService();
games = [];
constructor() {} constructor() {}
@@ -17,11 +26,58 @@ export class BoardComponent implements OnInit {
this.resetGame(); this.resetGame();
} }
async makeAiMove(){
const history = this.history.slice();
let squares = history[history.length - 1];
if(squares === undefined) {
squares = this.fields.slice();
}
const normalizedMoves = squares.map((v) => {
if (v === "X") {
return this.xIsNext ? 1 : -1;
} else if (v === "O") {
return this.xIsNext ? -1 : 1;
} else {
return 0;
}
});
let [move, moves] = await this.ai.doPredict(normalizedMoves);
while (squares[move] !== null && squares.includes(null)) {
console.log(`AI Failed - Spot ${move} - Resorting to next highest`);
// Make current move 0
moves[move] = 0;
move = moves.indexOf(Math.max(...moves));
// move = Math.floor(Math.random() * 9);
}
this.playerPress(move);
}
trainUp(playerLearn) {
console.log('Train Called - to be more like ', playerLearn);
const AllMoves = this.history.map((board) => {
return board.map((v) => {
if (v === playerLearn) {
return 1;
} else if (v === null) {
return 0;
} else {
return -1;
}
});
});
// const ai = new AiService();
this.games.push(this.ai.getMoves(AllMoves));
this.ai.trainOnGames(this.games, (data)=>{ console.log(data); });
}
resetGame() { resetGame() {
this.winner = null; this.winner = null;
this.draw = false; this.draw = false;
this.xIsNext = true; this.xIsNext = true;
this.fields = Array(9).fill(null); this.fields = Array(9).fill(null);
this.history = [];
} }
get player() { get player() {
@@ -36,6 +92,8 @@ export class BoardComponent implements OnInit {
if (!this.fields[i]) { if (!this.fields[i]) {
this.fields.splice(i, 1, this.player); this.fields.splice(i, 1, this.player);
this.history.push(this.fields.slice());
this.xIsNext = !this.xIsNext; this.xIsNext = !this.xIsNext;
this.calculateWinner(); this.calculateWinner();
} }
@@ -51,7 +109,7 @@ export class BoardComponent implements OnInit {
[0, 3, 6], [0, 3, 6],
[2, 4, 7], [1, 4, 7],
[2, 5, 8], [2, 5, 8],

View File

@@ -4,10 +4,11 @@ import { CommonModule } from '@angular/common';
import { BoardComponent } from './board.component'; import { BoardComponent } from './board.component';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { SquareModule } from '../square/square.module'; import { SquareModule } from '../square/square.module';
import { NbButtonModule } from '@nebular/theme';
@NgModule({ @NgModule({
declarations: [BoardComponent], declarations: [BoardComponent],
imports: [CommonModule, SharedModule, SquareModule], imports: [CommonModule, SharedModule, SquareModule, NbButtonModule],
exports: [BoardComponent] exports: [BoardComponent]
}) })
export class BoardModule {} export class BoardModule {}

View File

@@ -1,7 +1,7 @@
<div class="container"> <div class="container">
<h1 class="title"> <h1 class="title">
{{ 'PAGES.DETAIL.TITLE' | translate }} {{ 'PAGES.DETAIL.TITLE' }}
</h1> </h1>
<a routerLink="/">{{ 'PAGES.DETAIL.BACK_TO_HOME' | translate }}</a> <a routerLink="/">{{ 'PAGES.DETAIL.BACK_TO_HOME' }}</a>
</div> </div>

View File

@@ -4,7 +4,9 @@ import { Component, Input, OnInit } from '@angular/core';
selector: 'app-square', selector: 'app-square',
template: ` template: `
<button>{{ value }}</button> <button nbButton *ngIf="!value">{{ value }}</button>
<button nbButton hero status="success" *ngIf="value == 'X'">{{ value }}</button>
<button nbButton hero status="info" *ngIf="value == 'O'">{{ value }}</button>
`, `,
styles: [ styles: [
` `
@@ -16,6 +18,7 @@ import { Component, Input, OnInit } from '@angular/core';
button { button {
width: 100%; width: 100%;
height: 200px; height: 200px;
font-size: 5em !important;
} }
` `

View File

@@ -3,10 +3,11 @@ import { CommonModule } from '@angular/common';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { SquareComponent } from '../square/square.component'; import { SquareComponent } from '../square/square.component';
import { NbButtonModule } from '@nebular/theme';
@NgModule({ @NgModule({
declarations: [SquareComponent], declarations: [SquareComponent],
imports: [CommonModule, SharedModule], imports: [CommonModule, SharedModule, NbButtonModule],
exports: [SquareComponent] exports: [SquareComponent]
}) })
export class SquareModule {} export class SquareModule {}

View File

@@ -1,3 +1,10 @@
@use 'themes' as *;
@use '@nebular/theme/styles/globals' as *;
@include nb-install() {
@include nb-theme-global();
};
/* You can add global styles to this file, and also import other style files */ /* You can add global styles to this file, and also import other style files */
html, body { html, body {
margin: 0; margin: 0;

19
src/themes.scss Normal file
View File

@@ -0,0 +1,19 @@
@forward '@nebular/theme/styles/theming';
@use '@nebular/theme/styles/theming' as *;
@use '@nebular/theme/styles/themes/cosmic';
$nb-themes: nb-register-theme((
// add your variables here like:
// color-primary-100: #f2f6ff,
// color-primary-200: #d9e4ff,
// color-primary-300: #a6c1ff,
// color-primary-400: #598bff,
// color-primary-500: #3366ff,
// color-primary-600: #274bdb,
// color-primary-700: #1a34b8,
// color-primary-800: #102694,
// color-primary-900: #091c7a,
), cosmic, cosmic);

View File

@@ -4,7 +4,8 @@
"outDir": "../out-tsc/app", "outDir": "../out-tsc/app",
"baseUrl": "", "baseUrl": "",
"types": [ "types": [
"node" "node",
"@types/long"
] ]
}, },
"files": [ "files": [

View File

@@ -9,6 +9,7 @@
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"allowJs": true, "allowJs": true,
"skipLibCheck": true,
"target": "es5", "target": "es5",
"typeRoots": [ "typeRoots": [
"node_modules/@types" "node_modules/@types"