Compare commits
26 Commits
main
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
| 893ffe4aa4 | |||
| f71602f385 | |||
|
|
539b3c02f5 | ||
| 3ab800f967 | |||
| 69223a513f | |||
| 80e67f41d7 | |||
| c2aa53ac88 | |||
| 21ffed5566 | |||
| 1197fcbcad | |||
|
|
9e542c64ba | ||
|
|
907284b098 | ||
|
|
877f6cd529 | ||
|
|
13fe8d511e | ||
| 6eb2d78f8c | |||
|
|
6e97d11f43 | ||
|
|
0226cb9cfb | ||
|
|
ae1cd8944c | ||
|
|
88e25143bf | ||
|
|
a34dec4f43 | ||
|
|
f51f89e3b0 | ||
|
|
2a47eb6b20 | ||
|
|
85267bfe2c | ||
|
|
62bf421c9b | ||
|
|
fd9be97387 | ||
|
|
bc829346ae | ||
|
|
2e0c68a636 |
14
.devcontainer/Dockerfile
Normal file
14
.devcontainer/Dockerfile
Normal 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>"
|
||||||
55
.devcontainer/base.Dockerfile
Normal file
55
.devcontainer/base.Dockerfile
Normal 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>""
|
||||||
32
.devcontainer/devcontainer.json
Normal file
32
.devcontainer/devcontainer.json
Normal 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"
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
4897
package-lock.json
generated
4897
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@@ -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
164
src/app/ai.service.ts
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1 +1,15 @@
|
|||||||
<app-board></app-board>
|
<nb-layout>
|
||||||
|
|
||||||
|
<nb-layout-header fixed>
|
||||||
|
<!-- Insert header here -->
|
||||||
|
</nb-layout-header>
|
||||||
|
|
||||||
|
<nb-layout-column>
|
||||||
|
<app-board></app-board>
|
||||||
|
</nb-layout-column>
|
||||||
|
|
||||||
|
<nb-layout-footer fixed>
|
||||||
|
<!-- Insert footer here -->
|
||||||
|
</nb-layout-footer>
|
||||||
|
|
||||||
|
</nb-layout>
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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() {
|
||||||
@@ -29,13 +85,15 @@ export class BoardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
playerPress(i: number) {
|
playerPress(i: number) {
|
||||||
if(this.winner != null) {
|
if (this.winner != null) {
|
||||||
this.resetGame();
|
this.resetGame();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
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],
|
||||||
|
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|||||||
@@ -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
19
src/themes.scss
Normal 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);
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
"outDir": "../out-tsc/app",
|
"outDir": "../out-tsc/app",
|
||||||
"baseUrl": "",
|
"baseUrl": "",
|
||||||
"types": [
|
"types": [
|
||||||
"node"
|
"node",
|
||||||
|
"@types/long"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user