Modernize

This commit is contained in:
2020-02-10 22:22:25 +02:00
parent 38af6495d0
commit a270071cc3
28 changed files with 13500 additions and 8229 deletions

45
.eslintrc.js Normal file
View File

@@ -0,0 +1,45 @@
module.exports = {
env: {
browser: true,
es6: true
},
extends: [
"eslint:recommended",
"@nuxtjs/eslint-config-typescript",
// Uses the recommended rules from the @typescript-eslint/eslint-plugin
"plugin:@typescript-eslint/eslint-recommended",
// Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict
// with prettier
"prettier/@typescript-eslint",
// Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors.
// Make sure this is always the last configuration in the extends array.
"plugin:prettier/recommended"
],
globals: {
Atomics: "readonly",
SharedArrayBuffer: "readonly"
},
plugins: ["vue", "@typescript-eslint"],
rules: {
camelcase: "off",
"no-console": ["error", { allow: ["warn", "error"] }],
"no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
vars: "all",
args: "after-used",
ignoreRestSiblings: false
}
],
"getter-return": "off"
}
};

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
.nuxt/
dist/
node_modules/
static/ecmascript-explained-2019

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "sumbodules/ecmascript-explained-2019"]
path = sumbodules/ecmascript-explained-2019
url = git@github.com:GeorgeSG/ecmascript-explained-2019.git
[submodule "submodules/ecmascript-explained-2019"]
path = submodules/ecmascript-explained-2019
url = git@github.com:GeorgeSG/ecmascript-explained-2019.git

5
.prettierrc Normal file
View File

@@ -0,0 +1,5 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 100
}

View File

@@ -1,3 +0,0 @@
{
"printWidth": 120
}

View File

@@ -1,8 +0,0 @@
{
"files.exclude": {
"**/.nuxt/": true,
"**/dist/": true,
"**/node_modules/": true,
"submodules/": true
}
}

View File

@@ -1,8 +0,0 @@
clean:
rm -rf ./static/ecmascript-explained-2019
prod:
mkdir -p ./static
cd ./submodules/ecmascript-explained-2019 && make prod
cp -r ./submodules/ecmascript-explained-2019/dist ./static/ecmascript-explained-2019
yarn && yarn generate

View File

@@ -1,12 +1,3 @@
/*
Colors: ;
Light Blue: #19C4FF
Dark Blue: #0085B2
Light Orange: #FF8F19
Dark Orange: #B26009
Light Gray: #f5f5f5
*/
html,
body {
margin: 0;
@@ -14,7 +5,7 @@ body {
font-family: "Verdana", sans-serif;
font-size: 100%;
background: #f5f5f5;
background: $color-light-gray;
color: #333;
}
@@ -29,7 +20,7 @@ h1 {
margin-top: 3em;
margin-bottom: 0;
font-size: 2em;
color: #0085b2;
color: $color-dark-blue;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.75);
}

View File

@@ -0,0 +1,10 @@
$color-light-blue: #19C4FF;
$color-dark-blue: #0085B2;
$color-light-orange: #FF8F19;
$color-dark-orange: #B26009;
$color-gray: #bbb;
$color-light-gray: #f5f5f5;
$color-dark-gray: #666;
$color-white: #ffffff;

View File

@@ -5,21 +5,22 @@
:key="index"
:state="cellState"
:disabled="finished"
@select="$emit('place', index)")
@select="$emit('place', index)"
)
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import Cell from "./Cell/Cell.vue";
import { CellState } from "~/plugins/tic-tac-toe/cell-state";
import { Component, Vue, Prop } from 'vue-property-decorator';
import Cell from './Cell/Cell.vue';
import { CellState } from '~/lib/tic-tac-toe/cell-state';
@Component({ components: { Cell } })
export default class Board extends Vue {
@Prop({ required: true })
cellStates: CellState[];
readonly cellStates: CellState[];
@Prop({ required: false, default: false })
finished: boolean;
readonly finished: boolean;
}
</script>
@@ -30,8 +31,8 @@ export default class Board extends Vue {
margin: 0 auto;
width: 210px;
height: 210px;
background: #fff;
border-left: 1px solid #ccc;
border-top: 1px solid #ccc;
background: $color-white;
border-left: 1px solid $color-gray;
border-top: 1px solid $color-gray;
}
</style>

View File

@@ -1,26 +1,30 @@
<template lang="pug">
input.cell(:disabled="disabled" type="button" @click="$emit('select')" :class="classNames")
input.cell(:disabled="disabled" type="button" @click="onSelect" :class="classNames")
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { CellState } from "~/plugins/tic-tac-toe/cell-state";
import { Component, Vue, Prop } from 'vue-property-decorator';
import { CellState } from '~/lib/tic-tac-toe/cell-state';
@Component
export default class Cell extends Vue {
@Prop({ required: false, default: null })
state: CellState;
readonly state: CellState | null;
@Prop({ required: false, default: false })
disabled: boolean;
readonly disabled: boolean;
get classNames() {
return {
placed: this.state !== null,
x: this.state === "x",
o: this.state === "o"
x: this.state === 'x',
o: this.state === 'o'
};
}
onSelect() {
this.$emit('select');
}
}
</script>
@@ -28,15 +32,15 @@ export default class Cell extends Vue {
.cell {
width: 70px;
height: 70px;
background: #fff;
background: $color-white;
border-top: 0;
border-left: 0;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
border-right: 1px solid $color-gray;
border-bottom: 1px solid $color-gray;
&:not(.placed):not(:disabled):hover {
cursor: pointer;
background: #0085b2;
background: $color-dark-blue;
}
&:not(.placed):not(:disabled):active {

View File

@@ -10,47 +10,49 @@
label(:class="{ selected: difficulty === 'hard'}")
input#hard(type="radio" v-model="difficulty" name="difficulty" value="hard")
| hard
Board(:cellStates="cellStates" :finished="finished" @place="place")
button.new-game(@click="newGame") new game
p.result {{ lastResultString }}
.stats
h2 stats
table
tr
td wins
td.wins.count {{ wins }}
tr
td draws
td.draws.count {{ draws }}
tr
td losses
td.losses.count {{ losses }}
tbody
tr
td wins
td.wins.count {{ wins }}
tr
td draws
td.draws.count {{ draws }}
tr
td losses
td.losses.count {{ losses }}
</template>
<script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator";
import Board from "./Board/Board.vue";
import Game from "~/plugins/tic-tac-toe/game";
import { AI } from "~/plugins/tic-tac-toe/ai";
import Player from "~/plugins/tic-tac-toe/player";
import { CellState } from "~/plugins/tic-tac-toe/cell-state";
import { Component, Vue, Watch } from 'vue-property-decorator';
import Board from './Board/Board.vue';
import { Game } from '~/lib/tic-tac-toe/game';
import { AI } from '~/lib/tic-tac-toe/ai';
import { Player } from '~/lib/tic-tac-toe/player';
import { CellState } from '~/lib/tic-tac-toe/cell-state';
@Component({ components: { Board } })
export default class TicTacToe extends Vue {
wins: number = 0;
draws: number = 0;
losses: number = 0;
lastResultString: string = "";
private wins: number = 0;
private draws: number = 0;
private losses: number = 0;
private lastResultString: string = '';
cellStates: CellState[] = [];
finished: boolean = false;
private cellStates: CellState[] = [];
private finished: boolean = false;
difficulty: AI.Difficulty = "normal";
private difficulty: AI.Difficulty = 'normal';
private game: Game;
private ai: AI;
@Watch("difficulty", { immediate: true })
@Watch('difficulty', { immediate: true })
onDifficultyChange(newDifficulty: AI.Difficulty) {
if (this.ai) {
this.ai.difficulty = newDifficulty;
@@ -66,7 +68,7 @@ export default class TicTacToe extends Vue {
this.ai = new AI(this.game, this.difficulty);
this.cellStates = this.game.copyBoard();
this.finished = false;
this.lastResultString = "";
this.lastResultString = '';
}
place(index: number) {
@@ -94,10 +96,10 @@ export default class TicTacToe extends Vue {
switch (this.game.winner) {
case Player.HUMAN:
this.wins++;
this.lastResultString = "You win! Congratulations!";
this.lastResultString = 'You win! Congratulations!';
break;
case Player.AI:
this.lastResultString = "The AI won. Better luck next time!";
this.lastResultString = 'The AI won. Better luck next time!';
this.losses++;
break;
default:
@@ -113,16 +115,16 @@ export default class TicTacToe extends Vue {
cursor: pointer;
padding: 10px 20px;
margin-top: 10px;
background: #fff;
color: #0085b2;
background: $color-white;
color: $color-dark-blue;
text-decoration: none;
border: 1px solid #0085b2;
border: 1px solid $color-dark-blue;
border-radius: 4px;
&:hover {
background: #0085b2;
color: #fff;
background: $color-dark-blue;
color: $color-white;
}
&:active {
@@ -143,26 +145,26 @@ export default class TicTacToe extends Vue {
text-align: center;
font-size: 0.8em;
cursor: pointer;
background: #0085b2;
color: #fff;
border-bottom: 1px solid #00678a;
border-top: 1px solid #00678a;
background: $color-dark-blue;
color: $color-white;
border-bottom: 1px solid $color-dark-blue;
border-top: 1px solid $color-dark-blue;
&:first-of-type {
margin-left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
border: 1px solid #00678a;
border: 1px solid $color-dark-blue;
}
&:last-of-type {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
border: 1px solid #00678a;
border: 1px solid $color-dark-blue;
}
&:hover {
background: #00678a;
background: $color-dark-blue;
}
&:active {
@@ -176,11 +178,11 @@ export default class TicTacToe extends Vue {
> .selected {
cursor: default;
background: #ff8f19;
border-color: #b26009 !important;
background: $color-light-orange;
border-color: $color-dark-orange !important;
&:hover {
background: #ff8f19;
background: $color-light-orange;
}
&:active {
@@ -198,9 +200,9 @@ export default class TicTacToe extends Vue {
> h2 {
display: inline-block;
width: 200px;
color: #0085b2;
color: $color-dark-blue;
font-weight: normal;
border-bottom: 1px solid #0085b2;
border-bottom: 1px solid $color-dark-blue;
}
table {
@@ -217,21 +219,21 @@ export default class TicTacToe extends Vue {
display: inline-block;
min-width: 15px;
padding: 2px 3px;
color: #fff;
color: $color-white;
text-align: center;
font-size: 0.8em;
border-radius: 2px;
&.wins {
background: #0085b2;
background: $color-dark-blue;
}
&.losses {
background: #ff8f19;
background: $color-light-orange;
}
&.draws {
background: #666;
background: $color-dark-gray;
}
}
}

View File

@@ -1,9 +1,9 @@
import Game from "./game";
import { Game } from './game';
export class AI {
class AI {
private static readonly INFINITY = 9;
constructor(private game: Game, public difficulty: AI.Difficulty = "normal") {}
constructor(private game: Game, public difficulty: AI.Difficulty = 'normal') {}
move() {
const move = this.chooseMove();
@@ -12,15 +12,15 @@ export class AI {
private chooseMove(): number[] {
switch (this.difficulty) {
case "easy":
case 'easy':
return this.randomChoice(this.game);
case "normal":
case 'normal':
if (this.randomInt(0, 10) > 4) {
return this.alphabetaChoice(this.game);
} else {
return this.randomChoice(this.game);
}
case "hard":
case 'hard':
return this.alphabetaChoice(this.game);
}
}
@@ -134,6 +134,8 @@ export class AI {
}
}
export namespace AI {
export type Difficulty = "easy" | "normal" | "hard";
namespace AI {
export type Difficulty = 'easy' | 'normal' | 'hard';
}
export { AI };

View File

@@ -1,4 +1,4 @@
type Token = "x" | "o";
type Token = 'x' | 'o';
type CellState = null | Token;
export { CellState, Token };

View File

@@ -0,0 +1,3 @@
type Difficulty = 'easy' | 'medium' | 'hard';
export { Difficulty };

View File

@@ -1,7 +1,7 @@
import { CellState } from "./cell-state";
import Player from "./player";
import { CellState } from './cell-state';
import { Player } from './player';
export default class Game {
export class Game {
private static readonly WINNING_STATES = [
[0, 1, 2],
[3, 4, 5],

View File

@@ -0,0 +1,8 @@
import { Token } from './cell-state';
export class Player {
public static readonly HUMAN = new Player('x');
public static readonly AI = new Player('o');
constructor(public token: Token) {}
}

View File

@@ -1,40 +1,53 @@
export default {
buildModules: [
'@nuxt/typescript-build',
[
'@nuxtjs/google-analytics',
{
id: 'UA-135058128-1'
}
]
],
head: {
htmlAttrs: {
lang: "en"
lang: 'en'
},
title: "Georgi Gardev",
title: 'Georgi Gardev',
meta: [
{ charset: "utf-8" },
{ name: "author", content: "Georgi Gardev" },
{ name: "owner", content: "Georgi Gardev" },
{ name: "description", content: "Personal Homepage of Georgi Gardev" },
{ name: "copyright", content: "Georgi Gardev 2014" },
{ name: "robots", content: "index, follow" },
{ name: "revisit-after", content: "2 days" },
{ name: "GOOGLEBOT", content: "index, follow, all" },
{ name: "audience", content: "all" },
{ name: "viewport", content: "width=device-width, initial-scale=1" }
{ charset: 'utf-8' },
{ name: 'author', content: 'Georgi Gardev' },
{ name: 'owner', content: 'Georgi Gardev' },
{ name: 'description', content: 'Personal Homepage of Georgi Gardev' },
{ name: 'copyright', content: 'Georgi Gardev 2014' },
{ name: 'robots', content: 'index, follow' },
{ name: 'revisit-after', content: '2 days' },
{ name: 'GOOGLEBOT', content: 'index, follow, all' },
{ name: 'audience', content: 'all' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
]
},
css: [{ lang: "scss", src: "@/assets/styles/main.scss" }],
css: ['~/assets/styles/main.scss'],
modules: [
'@nuxtjs/style-resources',
[
"nuxt-fontawesome",
'nuxt-fontawesome',
{
component: "fa",
component: 'fa',
imports: [
{
set: "@fortawesome/free-solid-svg-icons",
icons: ["fas"]
set: '@fortawesome/free-solid-svg-icons',
icons: ['fas']
},
{
set: "@fortawesome/free-brands-svg-icons",
icons: ["fab"]
set: '@fortawesome/free-brands-svg-icons',
icons: ['fab']
}
]
}
]
],
plugins: [{ src: "~/plugins/ga.js", ssr: false }]
styleResources: {
scss: ['assets/styles/variables.scss']
}
};

13252
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,33 +2,48 @@
"name": "gardev.com",
"description": "Georgi Gardev's personal website. Hosted at gardev.com",
"version": "1.0.0",
"license": "UNLICENSED",
"private": true,
"author": {
"name": "Georgi Gardev",
"email": "georgi@gardev.com",
"url": "http://gardev.com"
},
"devDependencies": {
"node-sass": "^4.11.0",
"pug": "^2.0.3",
"pug-plain-loader": "^1.0.0",
"sass-loader": "^7.1.0",
"tslint-config-prettier": "^1.18.0",
"tslint-plugin-prettier": "^2.0.1"
"engines": {
"node": "12.15.0"
},
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^5.7.2",
"@fortawesome/free-solid-svg-icons": "^5.7.2",
"nuxt-fontawesome": "^0.4.0",
"nuxt-ts": "latest",
"vue-property-decorator": "^7.3.0"
},
"license": "UNLICENSED",
"scripts": {
"dev": "nuxt-ts",
"build": "nuxt-ts build",
"start": "nuxt-ts start",
"generate": "nuxt-ts generate",
"lint": "prettier -c '**/*'",
"format": "prettier --write '**/*'"
"start": "nuxt-ts start",
"test": "jest",
"lint": "eslint --ext .ts,.js,.vue .",
"lint-fix": "eslint --ext .ts,.js,.vue . --fix"
},
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^5.12.1",
"@fortawesome/free-solid-svg-icons": "^5.12.1",
"@nuxt/typescript-runtime": "^0.3.8",
"@nuxtjs/style-resources": "^1.0.0",
"nuxt": "^2.11.0",
"nuxt-fontawesome": "^0.4.0",
"vue-property-decorator": "^8.4.0"
},
"devDependencies": {
"@nuxt/typescript-build": "^0.5.6",
"@nuxtjs/eslint-config-typescript": "^1.0.2",
"@nuxtjs/google-analytics": "^2.2.3",
"@typescript-eslint/eslint-plugin": "^2.19.1",
"@typescript-eslint/parser": "^2.19.1",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-prettier": "^3.1.2",
"node-sass": "^4.13.1",
"prettier": "^1.19.1",
"pug": "^2.0.4",
"pug-plain-loader": "^1.0.0",
"sass-loader": "^8.0.2",
"tslint-config-prettier": "^1.18.0"
}
}

View File

@@ -25,8 +25,8 @@
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import TicTacToe from "~/components/TicTacToe/TicTacToe.vue";
import { Component, Vue } from 'vue-property-decorator';
import TicTacToe from '~/components/TicTacToe/TicTacToe.vue';
@Component({ components: { TicTacToe } })
export default class Home extends Vue {}
@@ -34,17 +34,17 @@ export default class Home extends Vue {}
<style lang="scss">
.social {
color: #bbb;
color: $color-gray;
text-decoration: none;
padding: 0 4px;
border-radius: 5px;
&:hover {
color: #0085b2;
color: $color-dark-blue;
}
&:active {
color: #006385;
color: $color-dark-blue;
}
}
</style>

View File

@@ -1,27 +0,0 @@
export default ({ app }) => {
/*
** Only run on client-side and only in production mode
*/
if (process.env.NODE_ENV !== 'production') return
/*
** Include Google Analytics Script
*/
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
/*
** Set the current page
*/
ga('create', 'UA-135058128-1', 'auto')
/*
** Every time the route changes (fired on initialization too)
*/
app.router.afterEach((to, from) => {
/*
** We tell Google Analytics to add a `pageview`
*/
ga('set', 'page', to.fullPath)
ga('send', 'pageview')
})
}

View File

@@ -1,3 +0,0 @@
type Difficulty = "easy" | "medium" | "hard";
export default Difficulty;

View File

@@ -1,8 +0,0 @@
import { Token } from "./cell-state";
export default class Player {
public static readonly HUMAN = new Player("x");
public static readonly AI = new Player("o");
constructor(public token: Token) {}
}

View File

@@ -1,16 +1,22 @@
{
"extends": "@nuxt/typescript",
"compilerOptions": {
"baseUrl": ".",
"target": "es6",
"module": "es2015",
"moduleResolution": "node",
"lib": ["esnext", "esnext.asynciterable", "dom"],
"esModuleInterop": true,
"experimentalDecorators": true,
"paths": {
"~/plugins/*": ["./plugins/*"],
"~/components/*": ["./components/*"],
"~/pages/*": ["./pages/*"],
"~/assets/*": ["./assets/*"]
},
"strictPropertyInitialization": false,
"allowJs": true,
"sourceMap": true,
"strict": true,
"types": ["@types/node", "@nuxt/vue-app"]
}
"noImplicitAny": false,
"noEmit": true,
"baseUrl": ".",
"paths": {
"~/*": ["./*"]
},
"types": ["@types/node", "@nuxt/types"]
},
"exclude": ["node_modules"]
}

View File

@@ -1,12 +0,0 @@
{
"defaultSeverity": "warning",
"extends": ["tslint:recommended", "tslint-config-prettier"],
"rules": {
"prettier": true,
"member-access": false,
"no-namespace": false,
"object-literal-sort-keys": false,
"ordered-imports": false
}
}

8009
yarn.lock

File diff suppressed because it is too large Load Diff