diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0fb9ddb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+
+# production
+/build
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..7d7e588
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,23 @@
+FROM nginx:latest
+
+RUN apt-get update && apt-get install -y nodejs npm
+
+WORKDIR /app
+
+COPY package*.json ./
+
+RUN npm install
+
+COPY . .
+
+RUN npm run build
+
+WORKDIR /usr/share/nginx/html
+COPY index.html .
+
+RUN mkdir -p /usr/share/nginx/html/public
+COPY src/*.css ./public
+
+RUN cp /app/build/* ./public
+
+EXPOSE 80
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..29e4f74
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,23 @@
+pipeline {
+ agent any
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+ stage('Build') {
+ steps {
+ script {
+ docker.build('test.trianta.dev:latest')
+ }
+ }
+ }
+ stage('Deploy') {
+ steps {
+ sh 'docker stop test && docker rm test || exit 0'
+ sh 'docker run -d -p 3466:80 --name test test.trianta.dev:latest'
+ }
+ }
+ }
+}
diff --git a/index.html b/index.html
index e7b82cb..0eb4389 100644
--- a/index.html
+++ b/index.html
@@ -3,10 +3,10 @@
Trianta
-
+
-
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..255b297
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,29 @@
+{
+ "name": "trianta.dev",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "trianta.dev",
+ "version": "0.1.0",
+ "license": "MIT",
+ "dependencies": {
+ "typescript": "^5.5.4"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..51f85b2
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "trianta.dev",
+ "version": "0.1.0",
+ "description": "My personal website",
+ "main": "index.html",
+ "scripts": {
+ "build": "tsc",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://lab.trianta.dev/Trianta/trianta.dev.git"
+ },
+ "author": "trianta",
+ "license": "MIT",
+ "dependencies": {
+ "typescript": "^5.5.4"
+ }
+}
diff --git a/css/home.css b/src/home.css
similarity index 96%
rename from css/home.css
rename to src/home.css
index 751f8bb..90107a7 100644
--- a/css/home.css
+++ b/src/home.css
@@ -125,8 +125,11 @@ a.button {
border-top-left-radius: 18px;
border-top-right-radius: 18px;
animation: card 3s linear infinite;
- max-width: 800px;
+ width: 800px;
+ height: 800px;
margin: 0px auto;
+ margin-top: 25px;
+ margin-bottom: 25px;
}
.cardTop {
diff --git a/css/pong.css b/src/pong.css
similarity index 100%
rename from css/pong.css
rename to src/pong.css
diff --git a/js/pong.js b/src/pong.js
similarity index 100%
rename from js/pong.js
rename to src/pong.js
diff --git a/src/snake.ts b/src/snake.ts
new file mode 100644
index 0000000..529befe
--- /dev/null
+++ b/src/snake.ts
@@ -0,0 +1,286 @@
+enum BoardState {
+ SNAKE = 0,
+ FOOD = 1,
+ CHECKED = 2 // For search algorithms
+}
+
+// Bit functions for ease
+function isBitSet(value: number, bit: number): boolean {
+ return ((value >> bit) % 2 != 0)
+}
+
+function bitSet(value: number, bit: number): number {
+ return value | 1 << bit;
+}
+
+function bitClear(value: number, bit: number): number {
+ return value & ~(1 << bit);
+}
+
+class Point {
+ x: number;
+ y: number;
+
+ constructor(x = 0, y = 0) {
+ this.x = x;
+ this.y = y;
+ }
+
+ copy(other: Point) {
+ this.x = other.x;
+ this.y = other.y;
+ }
+
+ add(other: Point) {
+ var result = new Point;
+ result.x = this.x + other.x;
+ result.y = this.y + other.y;
+ return result;
+ }
+
+ subtract(other: Point) {
+ var result = new Point;
+ result.x = this.x - other.x;
+ result.y = this.y - other.y;
+ return result;
+ }
+}
+
+class SnakeCore {
+ canvas: HTMLCanvasElement;
+ context: CanvasRenderingContext2D;
+ grid: number;
+ timeout: number;
+ width: number;
+ height: number;
+ board: number[][];
+ body: Point[];
+ gameover: boolean;
+ foodAte: boolean;
+
+ constructor() {
+ // TODO: Add CSS stuff for page
+ this.canvas = document.getElementById('snake') as HTMLCanvasElement;
+ this.context = this.canvas.getContext('2d') as CanvasRenderingContext2D;
+ this.grid = 25; // size of grid squares
+ this.timeout = 100; // speed in ms
+ this.width = 25;
+ this.height = 15;
+ this.board = [];
+ for (let i = 0; i < this.height; i++)
+ this.board.push(new Array(this.width));
+ this.body = [];
+ this.gameover = false;
+ this.foodAte = true;
+ this.body.push(new Point(12, 8));
+ this.board[8][12] = 1;
+ }
+
+ get head(): Point {
+ return this.body[this.body.length - 1];
+ }
+
+ reset() {
+ console.debug("[TRACE] Reset was triggered");
+ this.gameover = false;
+ this.foodAte = true;
+ for (let i = 0; i < this.height; i++) {
+ for (let j = 0; j < this.width; j++) {
+ this.board[i][j] = 0;
+ }
+ }
+ while (this.body.length > 0)
+ this.body.pop();
+ while (g_snakebot.path.length > 0)
+ g_snakebot.path.pop();
+ this.startRandom();
+ this.foodRegen();
+ this.draw();
+ }
+
+ startRandom() {
+ this.body.push(new Point(Math.floor(Math.random() * this.width), Math.floor(Math.random() * this.height)));
+ }
+
+ foodRegen() {
+ if (!this.foodAte)
+ return;
+ console.debug("[TRACE] Food reset was triggered");
+ this.foodAte = false;
+ while (true) {
+ let tmp = new Point(Math.floor(Math.random() * this.width), Math.floor(Math.random() * this.height));
+ if (isBitSet(this.board[tmp.y][tmp.x], BoardState.SNAKE))
+ continue;
+ this.board[tmp.y][tmp.x] = bitSet(this.board[tmp.y][tmp.x], BoardState.FOOD);
+ return;
+ }
+ }
+
+ // Simulate game logic
+ simulate() {
+ // Move snake
+ let next: Point = new Point;
+ next.copy(g_snakebot.nextMove());
+ if (next.x < 0 || next.x > g_snake.width || next.y < 0 || next.y > g_snake.height) {
+ g_snake.gameover = true;
+ return;
+ }
+ if (isBitSet(g_snake.board[next.y][next.x], BoardState.SNAKE) && (g_snake.body.length > 1)) {
+ g_snake.gameover = true; // Game should end (Snake touching snake)
+ return;
+ }
+
+ g_snake.board[next.y][next.x] = bitSet(g_snake.board[next.y][next.x], BoardState.SNAKE);
+ g_snake.body.push(next);
+ if (!isBitSet(g_snake.board[next.y][next.x], BoardState.FOOD)) {
+ let old: Point = g_snake.body.shift() as Point;
+ g_snake.board[old.y][old.x] = bitClear(g_snake.board[old.y][old.x], BoardState.SNAKE);
+ } else {
+ g_snake.board[next.y][next.x] = bitClear(g_snake.board[next.y][next.x], BoardState.FOOD);
+ g_snake.foodAte = true;
+ while (g_snakebot.path.length > 0)
+ g_snakebot.path.pop();
+ }
+ }
+
+ // Draw game to canvas
+ draw() {
+ // Clear the screen
+ g_snake.context.clearRect(0, 0, g_snake.canvas.width, g_snake.canvas.height);
+
+ // Draw game
+ for (let i = 0; i < g_snake.height; i++) {
+ for (let j = 0; j < g_snake.width; j++) {
+ if (isBitSet(g_snake.board[i][j], BoardState.SNAKE))
+ g_snake.context.fillStyle = "green";
+ else if (isBitSet(g_snake.board[i][j], BoardState.FOOD))
+ g_snake.context.fillStyle = "red";
+ else
+ g_snake.context.fillStyle = "black";
+ g_snake.context.fillRect(j * g_snake.grid, i * g_snake.grid, g_snake.grid, g_snake.grid);
+ }
+ }
+ }
+}
+
+class Bot {
+ pathUntrimmed: Point[];
+ path: Point[];
+
+ constructor() {
+ this.pathUntrimmed = [];
+ this.path = [];
+ }
+
+ bfs() {
+ var search: Point[] = [g_snake.head];
+ while (search.length !== 0) {
+ let current: Point = search.shift() as Point;
+ if (isBitSet(g_snake.board[current.y][current.x], BoardState.CHECKED))
+ continue;
+ this.pathUntrimmed.push(current);
+ let locals: Point[] = new Array(4);
+ for (var i = 0; i < 4; i++) {
+ locals[i] = new Point;
+ locals[i].copy(current);
+ }
+ locals[0].y += 1;
+ locals[1].x += 1;
+ locals[2].y -= 1;
+ locals[3].x -= 1;
+ for (const local of locals) {
+ if (local.x < 0 || local.x > g_snake.width - 1 || local.y < 0 || local.y > g_snake.height - 1)
+ continue;
+ let value = g_snake.board[local.y][local.x];
+ if (isBitSet(value, BoardState.FOOD)) {
+ this.pathUntrimmed.push(local);
+ return;
+ }
+ if (isBitSet(value, BoardState.CHECKED) || isBitSet(value, BoardState.SNAKE))
+ continue;
+ search.push(local);
+ }
+ g_snake.board[current.y][current.x] = bitSet(g_snake.board[current.y][current.x], BoardState.CHECKED);
+ }
+ }
+
+ // TODO: Fix trim function
+ trim() {
+ let reachedSnake = false;
+ this.path.push(this.pathUntrimmed.pop() as Point); // Push food location
+ while (this.pathUntrimmed.length !== 0) {
+ let location: Point = this.pathUntrimmed.pop();
+ if (reachedSnake)
+ continue;
+ if (isBitSet(g_snake.board[location.y][location.x], BoardState.SNAKE)) {
+ reachedSnake = true;
+ continue;
+ }
+ var delta = new Point;
+ delta = location.subtract(this.path[this.path.length - 1]);
+ if ((Math.abs(delta.x) + Math.abs(delta.y)) === 1)
+ this.path.push(location);
+ }
+ }
+
+ unvisit() {
+ for (let i = 0; i < g_snake.height; i++) {
+ for (let j = 0; j < g_snake.width; j++) {
+ g_snake.board[i][j] = bitClear(g_snake.board[i][j], BoardState.CHECKED);
+ }
+ }
+ }
+
+ /*
+ values:
+ 0 = left
+ 1 = up
+ 2 = right
+ 4 = down
+ */
+ nextMove() {
+ // Get new path to food
+ if (this.path.length === 0)
+ this.pathRefresh();
+ var next: Point = new Point;
+ next.copy(this.path.pop());
+ var delta = new Point;
+ delta = next.subtract(g_snake.head);
+ if (delta.x > 1)
+ console.log("[ERR] delta.x > 1");
+ else if (delta.x < -1)
+ console.log("[ERR] delta.x < 1");
+ if (delta.y > 1)
+ console.log("[ERR] delta.y > 1");
+ else if (delta.y < -1)
+ console.log("[ERR] delta.y < 1");
+ return next;
+ }
+
+ pathRefresh() {
+ this.bfs();
+ this.trim();
+ this.unvisit();
+ }
+}
+
+const g_snake: SnakeCore = new SnakeCore();
+const g_snakebot: Bot = new Bot();
+
+// game loop
+function snakeloop() {
+ // Reset of needed
+ if (g_snake.gameover)
+ g_snake.reset();
+
+ // Simulate movement of snake
+ g_snake.simulate();
+
+ // Regenerate food if needed
+ g_snake.foodRegen();
+
+ g_snake.draw();
+}
+
+// start the game
+setInterval(snakeloop, g_snake.timeout);
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..fe73056
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "outDir": "./build",
+ "allowJs": true,
+ "target": "es5"
+ },
+ "include": ["./src/**/*"]
+}