feat: initial tictactoe web app with CI/CD pipeline
Some checks failed
homelab-k8s-services/tictactoe/pipeline/head There was a failure building this commit

- Express app serving vanilla JS 2-player TicTacToe game
- Dockerfile (multi-stage node:18-slim)
- Jenkinsfile (K8s pod: test → Harbor push → Helm bump → Gitea push)
- Helm chart v1.0.0 with HTTPRoute for tictactoe.fireflylab.local

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-20 13:27:31 +07:00
commit b8783f8ee6
15 changed files with 5196 additions and 0 deletions

61
public/game.js Normal file
View File

@@ -0,0 +1,61 @@
(function () {
const WINS = [
[0,1,2],[3,4,5],[6,7,8],
[0,3,6],[1,4,7],[2,5,8],
[0,4,8],[2,4,6]
];
let board, current, over;
const cells = document.querySelectorAll('.cell');
const status = document.getElementById('status');
const resetBtn = document.getElementById('reset');
function init() {
board = Array(9).fill(null);
current = 'X';
over = false;
cells.forEach(c => {
c.textContent = '';
c.className = 'cell';
});
status.textContent = "Player X's turn";
}
function checkWinner() {
for (const [a, b, c] of WINS) {
if (board[a] && board[a] === board[b] && board[a] === board[c]) {
return { winner: board[a], line: [a, b, c] };
}
}
return board.every(Boolean) ? { winner: null, draw: true } : null;
}
function handleClick(e) {
const idx = parseInt(e.target.dataset.index);
if (over || board[idx]) return;
board[idx] = current;
const cell = cells[idx];
cell.textContent = current;
cell.classList.add('taken', current.toLowerCase());
const result = checkWinner();
if (result) {
over = true;
if (result.draw) {
status.textContent = "It's a draw!";
} else {
result.line.forEach(i => cells[i].classList.add('winner'));
status.textContent = `Player ${result.winner} wins!`;
}
} else {
current = current === 'X' ? 'O' : 'X';
status.textContent = `Player ${current}'s turn`;
}
}
cells.forEach(c => c.addEventListener('click', handleClick));
resetBtn.addEventListener('click', init);
init();
})();

28
public/index.html Normal file
View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TicTacToe</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Tic Tac Toe</h1>
<div id="status" class="status">Player X's turn</div>
<div id="board" class="board">
<div class="cell" data-index="0"></div>
<div class="cell" data-index="1"></div>
<div class="cell" data-index="2"></div>
<div class="cell" data-index="3"></div>
<div class="cell" data-index="4"></div>
<div class="cell" data-index="5"></div>
<div class="cell" data-index="6"></div>
<div class="cell" data-index="7"></div>
<div class="cell" data-index="8"></div>
</div>
<button id="reset" class="reset-btn">New Game</button>
</div>
<script src="game.js"></script>
</body>
</html>

91
public/style.css Normal file
View File

@@ -0,0 +1,91 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', sans-serif;
background: #1a1a2e;
color: #eee;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
text-align: center;
}
h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #e94560;
}
.status {
font-size: 1.2rem;
margin-bottom: 1.5rem;
min-height: 1.5em;
color: #a8dadc;
}
.board {
display: grid;
grid-template-columns: repeat(3, 120px);
grid-template-rows: repeat(3, 120px);
gap: 8px;
margin: 0 auto 1.5rem;
width: fit-content;
}
.cell {
background: #16213e;
border: 2px solid #0f3460;
border-radius: 8px;
font-size: 3rem;
font-weight: bold;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s;
user-select: none;
}
.cell:hover:not(.taken) {
background: #0f3460;
}
.cell.taken {
cursor: default;
}
.cell.x {
color: #e94560;
}
.cell.o {
color: #a8dadc;
}
.cell.winner {
background: #0f3460;
border-color: #e94560;
}
.reset-btn {
padding: 0.75rem 2rem;
font-size: 1rem;
background: #e94560;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.15s;
}
.reset-btn:hover {
background: #c73652;
}