feat: add session score display and refactor Jenkinsfile

Score tracks X wins, O wins, draws in-memory for current session.
Jenkinsfile rewritten as scripted pipeline with named execute functions
and per-branch strategy closures (mirrors BasePipeline pattern).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 14:53:42 +07:00
parent 9c5243290d
commit 474545b24e
4 changed files with 97 additions and 36 deletions

69
Jenkinsfile vendored
View File

@@ -2,44 +2,26 @@
import vn.fireflylab.pipeline.BranchStrategy import vn.fireflylab.pipeline.BranchStrategy
pipeline { // ── Stage functions ────────────────────────────────────────────────
agent { def executeInstallTest() {
kubernetes {
yaml homelabK8sAgent(withTools: true)
}
}
environment {
DOCKER_HOST = 'tcp://localhost:2375'
}
stages {
stage('Install & Test') { stage('Install & Test') {
steps { container('node') { runNodeTest() }
container('node') {
runNodeTest()
}
}
} }
}
stage('Build & Push Image') { def executeBuildPush() {
when { expression { BranchStrategy.shouldBuildImage(env.BRANCH_NAME) } } stage('Build & Push') {
steps {
container('docker') { container('docker') {
script {
def tag = BranchStrategy.imageTag(env.BRANCH_NAME) def tag = BranchStrategy.imageTag(env.BRANCH_NAME)
env.IMAGE_TAG = tag env.IMAGE_TAG = tag
dockerBuildPush(appName: 'tictactoe', tag: tag) dockerBuildPush(appName: 'tictactoe', tag: tag)
} }
} }
} }
}
def executeBumpChart() {
stage('Bump Helm Chart') { stage('Bump Helm Chart') {
when { expression { BranchStrategy.shouldBumpChart(env.BRANCH_NAME) } }
steps {
container('tools') { container('tools') {
script {
bumpHelmChart(imageTag: env.IMAGE_TAG) bumpHelmChart(imageTag: env.IMAGE_TAG)
gitCommitPush( gitCommitPush(
files: ['manifest/helm/Chart.yaml', 'manifest/helm/values.yaml'], files: ['manifest/helm/Chart.yaml', 'manifest/helm/values.yaml'],
@@ -47,6 +29,41 @@ pipeline {
) )
} }
} }
}
// ── Pipeline ───────────────────────────────────────────────────────
podTemplate(yaml: homelabK8sAgent(withTools: true)) {
node(POD_LABEL) {
withEnv(['DOCKER_HOST=tcp://localhost:2375']) {
checkout scm
BranchStrategy.prStrategy(env.BRANCH_NAME) {
executeInstallTest()
}
BranchStrategy.featureStrategy(env.BRANCH_NAME) {
executeInstallTest()
executeBuildPush()
}
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()
} }
} }
} }

View File

@@ -6,9 +6,13 @@
]; ];
let board, current, over; let board, current, over;
const score = { X: 0, O: 0, draw: 0 };
const cells = document.querySelectorAll('.cell'); const cells = document.querySelectorAll('.cell');
const status = document.getElementById('status'); const status = document.getElementById('status');
const resetBtn = document.getElementById('reset'); 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() { function init() {
board = Array(9).fill(null); board = Array(9).fill(null);
@@ -43,8 +47,12 @@
if (result) { if (result) {
over = true; over = true;
if (result.draw) { if (result.draw) {
score.draw++;
scoreDraw.textContent = score.draw;
status.textContent = "It's a draw!"; status.textContent = "It's a draw!";
} else { } else {
score[result.winner]++;
(result.winner === 'X' ? scoreX : scoreO).textContent = score[result.winner];
result.line.forEach(i => cells[i].classList.add('winner')); result.line.forEach(i => cells[i].classList.add('winner'));
status.textContent = `Player ${result.winner} wins!`; status.textContent = `Player ${result.winner} wins!`;
} }

View File

@@ -9,6 +9,11 @@
<body> <body>
<div class="container"> <div class="container">
<h1>Tic Tac Toe</h1> <h1>Tic Tac Toe</h1>
<div class="scoreboard">
<div class="score-item"><span class="score-label">X</span><span id="score-x" class="score-value">0</span></div>
<div class="score-item"><span class="score-label">Draw</span><span id="score-draw" class="score-value">0</span></div>
<div class="score-item"><span class="score-label">O</span><span id="score-o" class="score-value">0</span></div>
</div>
<div id="status" class="status">Player X's turn</div> <div id="status" class="status">Player X's turn</div>
<div id="board" class="board"> <div id="board" class="board">
<div class="cell" data-index="0"></div> <div class="cell" data-index="0"></div>

View File

@@ -75,6 +75,37 @@ h1 {
border-color: #e94560; 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 { .reset-btn {
padding: 0.75rem 2rem; padding: 0.75rem 2rem;
font-size: 1rem; font-size: 1rem;