diff --git a/Jenkinsfile b/Jenkinsfile index cdc99b3..d576377 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,51 +2,68 @@ import vn.fireflylab.pipeline.BranchStrategy -pipeline { - agent { - kubernetes { - yaml homelabK8sAgent(withTools: true) +// ── Stage functions ──────────────────────────────────────────────── +def executeInstallTest() { + stage('Install & Test') { + container('node') { runNodeTest() } + } +} + +def executeBuildPush() { + stage('Build & Push') { + container('docker') { + def tag = BranchStrategy.imageTag(env.BRANCH_NAME) + env.IMAGE_TAG = tag + dockerBuildPush(appName: 'tictactoe', tag: tag) } } +} - environment { - DOCKER_HOST = 'tcp://localhost:2375' +def executeBumpChart() { + stage('Bump Helm Chart') { + container('tools') { + bumpHelmChart(imageTag: env.IMAGE_TAG) + gitCommitPush( + files: ['manifest/helm/Chart.yaml', 'manifest/helm/values.yaml'], + message: "ci: bump tictactoe chart to ${env.IMAGE_TAG}" + ) + } } +} - stages { - stage('Install & Test') { - steps { - container('node') { - runNodeTest() - } +// ── Pipeline ─────────────────────────────────────────────────────── +podTemplate(yaml: homelabK8sAgent(withTools: true)) { + node(POD_LABEL) { + withEnv(['DOCKER_HOST=tcp://localhost:2375']) { + checkout scm + + BranchStrategy.prStrategy(env.BRANCH_NAME) { + executeInstallTest() } - } - stage('Build & Push Image') { - when { expression { BranchStrategy.shouldBuildImage(env.BRANCH_NAME) } } - steps { - container('docker') { - script { - def tag = BranchStrategy.imageTag(env.BRANCH_NAME) - env.IMAGE_TAG = tag - dockerBuildPush(appName: 'tictactoe', tag: tag) - } - } + BranchStrategy.featureStrategy(env.BRANCH_NAME) { + executeInstallTest() + executeBuildPush() } - } - stage('Bump Helm Chart') { - when { expression { BranchStrategy.shouldBumpChart(env.BRANCH_NAME) } } - steps { - container('tools') { - script { - bumpHelmChart(imageTag: env.IMAGE_TAG) - gitCommitPush( - files: ['manifest/helm/Chart.yaml', 'manifest/helm/values.yaml'], - message: "ci: bump tictactoe chart to ${env.IMAGE_TAG}" - ) - } - } + BranchStrategy.developStrategy(env.BRANCH_NAME) { + executeInstallTest() + executeBuildPush() + } + + BranchStrategy.mainStrategy(env.BRANCH_NAME) { + executeInstallTest() + executeBuildPush() + } + + BranchStrategy.releaseStrategy(env.BRANCH_NAME) { + executeInstallTest() + executeBuildPush() + } + + BranchStrategy.hotfixStrategy(env.BRANCH_NAME) { + executeInstallTest() + executeBuildPush() } } } diff --git a/public/game.js b/public/game.js index 1d74da8..bff7127 100644 --- a/public/game.js +++ b/public/game.js @@ -6,9 +6,13 @@ ]; let board, current, over; + const score = { X: 0, O: 0, draw: 0 }; const cells = document.querySelectorAll('.cell'); const status = document.getElementById('status'); const resetBtn = document.getElementById('reset'); + const scoreX = document.getElementById('score-x'); + const scoreO = document.getElementById('score-o'); + const scoreDraw = document.getElementById('score-draw'); function init() { board = Array(9).fill(null); @@ -43,8 +47,12 @@ if (result) { over = true; if (result.draw) { + score.draw++; + scoreDraw.textContent = score.draw; status.textContent = "It's a draw!"; } else { + score[result.winner]++; + (result.winner === 'X' ? scoreX : scoreO).textContent = score[result.winner]; result.line.forEach(i => cells[i].classList.add('winner')); status.textContent = `Player ${result.winner} wins!`; } diff --git a/public/index.html b/public/index.html index ec290d4..3f399ef 100644 --- a/public/index.html +++ b/public/index.html @@ -9,6 +9,11 @@

Tic Tac Toe

+
+
X0
+
Draw0
+
O0
+
Player X's turn
diff --git a/public/style.css b/public/style.css index 98306a8..7b2b937 100644 --- a/public/style.css +++ b/public/style.css @@ -75,6 +75,37 @@ h1 { border-color: #e94560; } +.scoreboard { + display: flex; + justify-content: center; + gap: 2rem; + margin-bottom: 1.5rem; +} + +.score-item { + display: flex; + flex-direction: column; + align-items: center; + background: #16213e; + border: 2px solid #0f3460; + border-radius: 8px; + padding: 0.5rem 1.5rem; + min-width: 70px; +} + +.score-label { + font-size: 0.85rem; + color: #a8dadc; + text-transform: uppercase; + letter-spacing: 1px; +} + +.score-value { + font-size: 2rem; + font-weight: bold; + color: #e94560; +} + .reset-btn { padding: 0.75rem 2rem; font-size: 1rem;