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({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
width: 650,
|
||||
height: 700,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/common": "13.3.1",
|
||||
"@angular/compiler": "13.3.1",
|
||||
"@angular/core": "13.3.1",
|
||||
"@angular/forms": "13.3.1",
|
||||
"@angular/language-service": "13.3.1",
|
||||
"@angular/platform-browser": "13.3.1",
|
||||
"@angular/platform-browser-dynamic": "13.3.1",
|
||||
"@angular/router": "13.3.1",
|
||||
"@angular/animations": "13.3.11",
|
||||
"@angular/cdk": "^13.0.0",
|
||||
"@angular/common": "13.3.11",
|
||||
"@angular/compiler": "13.3.11",
|
||||
"@angular/core": "13.3.11",
|
||||
"@angular/forms": "13.3.11",
|
||||
"@angular/language-service": "13.3.11",
|
||||
"@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",
|
||||
"tslib": "^2.1.0",
|
||||
"zone.js": "~0.11.5"
|
||||
@@ -66,12 +72,14 @@
|
||||
"@angular-eslint/schematics": "13.1.0",
|
||||
"@angular-eslint/template-parser": "13.1.0",
|
||||
"@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/http-loader": "7.0.0",
|
||||
"@playwright/test": "1.20.2",
|
||||
"@schematics/angular": "13.3.1",
|
||||
"@types/jasmine": "3.10.3",
|
||||
"@types/jasminewd2": "2.0.10",
|
||||
"@types/long": "4.0.2",
|
||||
"@types/node": "17.0.23",
|
||||
"@typescript-eslint/eslint-plugin": "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 { 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
|
||||
@@ -34,7 +37,11 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl
|
||||
useFactory: httpLoaderFactory,
|
||||
deps: [HttpClient]
|
||||
}
|
||||
})
|
||||
}),
|
||||
BrowserAnimationsModule,
|
||||
NbThemeModule.forRoot({ name: 'cosmic' }),
|
||||
NbLayoutModule,
|
||||
NbEvaIconsModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<h1>Tic-Tac-Toe</h1>
|
||||
</section>
|
||||
<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>
|
||||
<h2 *ngIf="winner">Gewinner: {{ winner }}<br /></h2><br />
|
||||
</section>
|
||||
@@ -10,3 +13,4 @@
|
||||
<app-square class="square-container h-48" *ngFor="let val of fields; let i=index" [value]="val"
|
||||
(click)="playerPress(i)"></app-square>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -40,6 +40,15 @@ describe('BoardComponent', () => {
|
||||
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 { AiService } from '../ai.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-board',
|
||||
@@ -10,6 +15,10 @@ export class BoardComponent implements OnInit {
|
||||
draw: boolean;
|
||||
xIsNext: boolean;
|
||||
fields: any[];
|
||||
history: any[];
|
||||
ai = new AiService();
|
||||
|
||||
games = [];
|
||||
|
||||
constructor() {}
|
||||
|
||||
@@ -17,11 +26,58 @@ export class BoardComponent implements OnInit {
|
||||
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() {
|
||||
this.winner = null;
|
||||
this.draw = false;
|
||||
this.xIsNext = true;
|
||||
this.fields = Array(9).fill(null);
|
||||
this.history = [];
|
||||
}
|
||||
|
||||
get player() {
|
||||
@@ -29,13 +85,15 @@ export class BoardComponent implements OnInit {
|
||||
}
|
||||
|
||||
playerPress(i: number) {
|
||||
if(this.winner != null) {
|
||||
if (this.winner != null) {
|
||||
this.resetGame();
|
||||
return;
|
||||
}
|
||||
if (!this.fields[i]) {
|
||||
this.fields.splice(i, 1, this.player);
|
||||
|
||||
this.history.push(this.fields.slice());
|
||||
|
||||
this.xIsNext = !this.xIsNext;
|
||||
this.calculateWinner();
|
||||
}
|
||||
@@ -51,7 +109,7 @@ export class BoardComponent implements OnInit {
|
||||
|
||||
[0, 3, 6],
|
||||
|
||||
[2, 4, 7],
|
||||
[1, 4, 7],
|
||||
|
||||
[2, 5, 8],
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ import { CommonModule } from '@angular/common';
|
||||
import { BoardComponent } from './board.component';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { SquareModule } from '../square/square.module';
|
||||
import { NbButtonModule } from '@nebular/theme';
|
||||
|
||||
@NgModule({
|
||||
declarations: [BoardComponent],
|
||||
imports: [CommonModule, SharedModule, SquareModule],
|
||||
imports: [CommonModule, SharedModule, SquareModule, NbButtonModule],
|
||||
exports: [BoardComponent]
|
||||
})
|
||||
export class BoardModule {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="container">
|
||||
<h1 class="title">
|
||||
{{ 'PAGES.DETAIL.TITLE' | translate }}
|
||||
{{ 'PAGES.DETAIL.TITLE' }}
|
||||
</h1>
|
||||
|
||||
<a routerLink="/">{{ 'PAGES.DETAIL.BACK_TO_HOME' | translate }}</a>
|
||||
<a routerLink="/">{{ 'PAGES.DETAIL.BACK_TO_HOME' }}</a>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,9 @@ import { Component, Input, OnInit } from '@angular/core';
|
||||
selector: 'app-square',
|
||||
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: [
|
||||
`
|
||||
@@ -16,6 +18,7 @@ import { Component, Input, OnInit } from '@angular/core';
|
||||
button {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
font-size: 5em !important;
|
||||
|
||||
}
|
||||
`
|
||||
|
||||
@@ -3,10 +3,11 @@ import { CommonModule } from '@angular/common';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { SquareComponent } from '../square/square.component';
|
||||
import { NbButtonModule } from '@nebular/theme';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SquareComponent],
|
||||
imports: [CommonModule, SharedModule],
|
||||
imports: [CommonModule, SharedModule, NbButtonModule],
|
||||
exports: [SquareComponent]
|
||||
})
|
||||
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 */
|
||||
html, body {
|
||||
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",
|
||||
"baseUrl": "",
|
||||
"types": [
|
||||
"node"
|
||||
"node",
|
||||
"@types/long"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "es5",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
|
||||
Reference in New Issue
Block a user