HTML+JS+CSS制作一個數獨游戲

閑來無事,用HTML+JS+CSS制作了一個數獨游戲消遣。其實主要是自己做題的時候用筆畫刪除數字太容易出錯,所以想搞一個程序稍微輔助一下。通過制作這個程序,反而提高了手工做題的水平,至少學會了記錄步數以便于回退。
20250710功能更新:用戶確定的單元格同時顯示單元格值以及該單元格是第幾步確定的,便于測試錯誤時回退;增加唯一可用數字高亮顯示功能,幫助找到確定數據的單元格位置;增加撤銷到指定步數功能,便于快速回撤到本次試探的開始位置。

0711:補上了手工輸入題目的功能。

1、游戲的界面(舊界面):

2、游戲的玩法(新界面):

3、游戲結束時彈出提示框(舊界面):

下面是游戲的全部代碼。其中HTML負責UI構造,CSS負責UI的顯示,JS包含了游戲的全部邏輯。

1、sudokuUI.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>數獨游戲</title><link rel="stylesheet" href="sudoku_style.css" />
</head>
<body><h1>數獨游戲</h1><div class="sudoku-container"><table id="sudoku-board"></table><div class="buttons"><button id="new-game" class="single-line-button">新題</button><button id="reset-btn" class="single-line-button">重置</button><button id="answer-btn" class="single-line-button">答案</button><br/><button id="highlight-unique-btn"  class="double-line-button">高亮唯一<br/>可用數字</button><button id="go-back-btn" class="double-line-button">回退至<br/>指定步數</button><button id="input-new-game" class="double-line-button">手工輸入<br/>新數獨題</button></div></div><dialog id="gridDialog"><h3>在每個單元格中輸入1-9的數字</h3><table id="gridTable"></table><button id="submitGrid">提交</button></dialog><script src="sudoku_script.js"></script>
</body>
</html>

2、sudoku_style.css

body {font-family: Arial, sans-serif;text-align: center;background-color: #f4f4f4;}h1 {margin-top: 30px;}.sudoku-container {display: inline-block;margin-top: 20px;padding: 20px;background: beige;border-radius: 10px;box-shadow: 0 0 15px rgba(0,0,0,0.1);}#sudoku-board {border-collapse: collapse;margin: 0 auto;}#sudoku-board td {width: 40px;height: 40px;text-align: center;vertical-align: middle;border: 1px solid #999;font-size: 18px;cursor: pointer;transition: background 0.2s ease;position: relative;padding: 0;}.cell-step {font-size: 12px;color: orange;margin-top: 2px;line-height: 1;text-align: center;}.sudoku-mini-grid {display: grid;grid-template-columns: repeat(3, 1fr);grid-template-rows: repeat(3, 1fr);width: 100%;height: 100%;}.mini-cell {font-size: 11px;color: #222;display: flex;align-items: center;justify-content: center;width: 100%;height: 100%;user-select: none;cursor: pointer;transition: background 0.2s, color 0.2s;}.mini-cell.gray {color: #bbb;
}.mini-cell.highlight {background: #b3e0ff !important;color: #222;border-radius: 5px;
}.mini-cell.black {color: #222;}.mini-cell.yellow {color: #ff0;}.mini-cell:hover:not(.gray) {background: #e0e0e0;}.sudoku-cell-fixed {font-size: 24px;font-weight: bold;color: #1976d2;display: flex;align-items: center;justify-content: center;height: 100%;}.sudoku-cell-user{font-size: 24px;font-weight: bold;color: #388e3c;display: flex;align-items: center;justify-content: center;height: 100%;}.sudoku-mini-grid {width: 100%;height: 100%;pointer-events: auto;}#sudoku-board tr:nth-child(3n) td {border-bottom: 2px solid #000;}#sudoku-board td:nth-child(3n) {border-right: 2px solid #000;}#sudoku-board tr:first-child td {border-top: 2px solid #000;}#sudoku-board td:first-child {border-left: 2px solid #000;}#sudoku-board .preset {background-color: #e0e0e0;font-weight: bold;}#sudoku-board .user-input {background-color: #fff;}#sudoku-board .error {color: red;}.buttons {margin-top: 20px;}.buttons button {margin: 10px;font-size: 16px;cursor: pointer;background-color: #4CAF50;color: white;border: none;border-radius: 5px;transition: background 0.2s ease;vertical-align: middle;}.single-line-button {padding: 10px 20px;}.double-line-button {padding: 5px;}.buttons button:hover {background-color: #45a049;}.buttons button:active {background-color: #3e8e41;}.sudoku-cell-fixed.red, .sudoku-cell-user.red { color: red; }.cell {width:28px;height:28px;
}

3、sudoku_script.js

// =====================
// 數獨游戲核心腳本
// =====================// 獲取頁面上的主要DOM元素
const boardElement = document.getElementById("sudoku-board"); // 數獨主棋盤容器
const newGameBtn = document.getElementById("new-game");      // 新游戲按鈕
const resetBtn = document.getElementById("reset-btn");      // 重置按鈕
const answerBtn = document.getElementById("answer-btn");    // 查看答案按鈕
const highlightUniqueBtn = document.getElementById("highlight-unique-btn"); // 高亮唯一解按鈕
const goBackBtn = document.getElementById("go-back-btn");   // 撤銷步數按鈕
const inputDialog = document.getElementById('gridDialog');         // 自定義題目輸入對話框
const openBtn = document.getElementById('input-new-game'); // 打開自定義題目按鈕
const inputTtable = document.getElementById('gridTable');           // 自定義題目表格
const submitBtn = document.getElementById('submitGrid');   // 提交自定義題目按鈕// 撤銷到指定步數
// 用戶點擊“撤銷”按鈕時觸發
// 會彈窗讓用戶輸入目標步數,撤銷到該步
// 步數小于1或大于當前步數無效
// 會清除多余步數填入的格子
// 完成后刷新棋盤
goBackBtn.addEventListener("click", () => {if (stepCounter <= 1) {alert("沒有可撤銷的步數");return;}let input = prompt("輸入要撤銷到的目標步數,應為1~" + (stepCounter - 1) + "之間的數字");if (input === null) return; // 用戶取消let targetStep = parseInt(input.trim(), 10);if (isNaN(targetStep) || targetStep < 1 || targetStep >= stepCounter) {alert("輸入的步數不存在");return;}for (let r = 0; r < 9; r++) {for (let c = 0; c < 9; c++) {if (userSteps[r][c] !== null && userSteps[r][c] > targetStep) {currentBoard[r][c] = null;  // 清除已填入的數據userSteps[r][c] = null;  // 清除該單元格對應的步數}}}stepCounter = targetStep + 1;drawBoard();
});// 重置按鈕事件
// 恢復到初始題面,清空用戶所有填寫和計時,刷新棋盤
resetBtn.addEventListener("click", () => {// 初始化相關變量currentBoard = JSON.parse(JSON.stringify(originalBoard));userSteps = Array.from({ length: 9 }, () => Array(9).fill(null));stepCounter = 1;redCells = new Set();timerStart = null;timerUsed = 0;// 重繪盤面drawBoard();
});// 默認初始題面,此題號稱超五星難度。可被新題或自定義題替換
let originalBoard = [[null, null, null, 3, null, null, 1, null, null],[5, null, null, 4, null, null, null, 9, null],[null, null, null, null, 2, 8, 6, null, null],[null, 9, null, null, null, null, null, null, 1],[null, null, 8, null, null, 7, null, null, 2],[null, 1, null, null, 4, null, 8, null, null],[null, null, 4, null, 8, 5, null, null, null],[3, null, null, 1, null, null, null, 4, null],[null, null, 2, 6, null, null, null, null, null]
];let currentBoard = JSON.parse(JSON.stringify(originalBoard)); // 當前棋盤狀態
// 步數記錄:userSteps[row][col] = step(第幾步),未填為 null
let userSteps = Array.from({length: 9}, () => Array(9).fill(null)); // 用戶每步填寫記錄
let stepCounter = 1; // 當前步數,從1開始
// 保存被標紅的格子,格式如:'row,col'
let redCells = new Set(); // 標記紅色警示格
// 計時相關
let timerStart = null; // 計時起點
let timerUsed = 0;     // 用時(秒)// 渲染棋盤主函數
// 根據currentBoard和用戶操作,動態生成HTML并綁定事件
function drawBoard() {boardElement.innerHTML = "";for (let row = 0; row < 9; row++) {const tr = document.createElement("tr");for (let col = 0; col < 9; col++) {const td = document.createElement("td");td.dataset.row = row;td.dataset.col = col;// 已確定數字if (currentBoard[row][col]) {const isPreset = originalBoard[row][col];let cellClass = isPreset ? 'sudoku-cell-fixed' : 'sudoku-cell-user';if (redCells.has(row + ',' + col)) {cellClass += ' red';}let stepHtml = '';if (!isPreset && userSteps[row][col] !== null) {stepHtml = `<div class="cell-step">${userSteps[row][col]}</div>`;}td.innerHTML = `<div class="${cellClass}">${currentBoard[row][col]}<br>${stepHtml}</div>`;// 右鍵取消td.oncontextmenu = function (e) {e.preventDefault();if (!isPreset) {// 記錄當前格子的步數const removedStep = userSteps[row][col];currentBoard[row][col] = null;userSteps[row][col] = null;// 所有步數大于 removedStep 的格子步數-1for (let r = 0; r < 9; r++) {for (let c = 0; c < 9; c++) {if (userSteps[r][c] !== null && userSteps[r][c] > removedStep) {userSteps[r][c]--;}}}stepCounter--;redCells.delete(row + ',' + col);drawBoard();}};// 左鍵在紅色與非紅色之間切換td.onclick = function (e) {if (e.button === 0) {if (!redCells.has(row + ',' + col)) {redCells.add(row + ',' + col);drawBoard();} else {redCells.delete(row + ',' + col);drawBoard();}}};} else {// 未確定,渲染9小格const miniGrid = document.createElement('div');miniGrid.className = 'sudoku-mini-grid';for (let k = 1; k <= 9; k++) {const miniCell = document.createElement('div');miniCell.className = 'mini-cell';miniCell.textContent = k;// 判斷是否可選if (isValidCell(row, col, k)) {miniCell.classList.add('black');// 左鍵點擊將該數字作為單元格值miniCell.onmousedown = function (e) {e.preventDefault();// 僅在首次確定未確定格時啟動計時if (currentBoard[row][col] === null && timerStart === null) {timerStart = Date.now();}currentBoard[row][col] = k;userSteps[row][col] = stepCounter++;drawBoard();};} else {miniCell.classList.add('gray');  // 同行、同列或同宮已出現的數字不可用}miniGrid.appendChild(miniCell);}td.appendChild(miniGrid);}tr.appendChild(td);}boardElement.appendChild(tr);}// 檢查是否全部填滿let allFilled = true;for (let row = 0; row < 9; row++) {for (let col = 0; col < 9; col++) {if (!currentBoard[row][col]) allFilled = false;}}if (allFilled) {if (timerStart !== null) {timerUsed = Math.round((Date.now() - timerStart) / 1000);setTimeout(() => { alert(`恭喜您解決了本題,共計耗時${timerUsed}秒!`); timerStart = null; }, 100);}}
}// 生成唯一解數獨新題
// 新游戲按鈕事件
// 自動生成一個唯一解的新題目,重置所有狀態
newGameBtn.addEventListener("click", async () => {// 生成唯一解數獨let puzzle;userSteps = Array.from({ length: 9 }, () => Array(9).fill(null));stepCounter = 1;do {puzzle = generateSudokuPuzzle();} while (!puzzle || countSolutions(puzzle) !== 1);originalBoard = puzzle;currentBoard = JSON.parse(JSON.stringify(originalBoard));redCells.clear();timerStart = null;drawBoard();
});// 高亮顯示唯一可用數字
// 高亮顯示所有唯一可填數字的格子
highlightUniqueBtn.addEventListener("click", () => {// 遍歷所有格子for (let row = 0; row < 9; row++) {for (let col = 0; col < 9; col++) {const cell = getUniqNumberCell(row, col);if (cell) {cell.classList.add('highlight');}}}
});// 生成完整解
// 隨機生成一個完整的數獨解(9x9的填滿且合法的盤面)
// 隨機生成一個完整的、合法的數獨解(遞歸回溯)
function generateFullSolution() {// 創建一個9x9的空棋盤,所有格子初始為nulllet board = Array.from({ length: 9 }, () => Array(9).fill(null));// 遞歸回溯填充函數,從左上角(0,0)開始function fill(row, col) {// 如果行號越界,說明已填滿整盤,返回trueif (row === 9) return true;// 計算下一個要填的格子的行列號let nextRow = col === 8 ? row + 1 : row;let nextCol = col === 8 ? 0 : col + 1;// 1~9隨機順序嘗試,增加解的多樣性let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9].sort(() => Math.random() - 0.5);for (let num of nums) {// 判斷num是否可以填入當前格(不違反數獨規則)if (isValidForBoard(board, row, col, num)) {board[row][col] = num; // 填入數字// 遞歸填下一個格子,若成功則整盤可解if (fill(nextRow, nextCol)) return true;board[row][col] = null; // 回溯,撤銷填入}}// 1~9都不行,說明此路不通,返回falsereturn false;}// 從(0,0)開始填盤fill(0, 0);return board; // 返回填好的完整解
}// 隨機挖空,生成題目
// 由完整解隨機挖空,生成唯一解題目
function generateSudokuPuzzle() {let solution = generateFullSolution();let puzzle = JSON.parse(JSON.stringify(solution));// 隨機順序挖空let cells = [];for (let r = 0; r < 9; r++) for (let c = 0; c < 9; c++) cells.push([r, c]);cells = cells.sort(() => Math.random() - 0.5);for (let i = 0; i < 60; i++) { // 最多挖60個空let [r, c] = cells[i];let backup = puzzle[r][c];puzzle[r][c] = null;// 挖空后如果解不唯一,撤回if (countSolutions(puzzle) !== 1) puzzle[r][c] = backup;}return puzzle;
}// 判斷唯一解
// 判斷給定棋盤的解的個數(遞歸回溯,計數)
function countSolutions(board) {let count = 0;let b = JSON.parse(JSON.stringify(board));function dfs() {for (let r = 0; r < 9; r++) {for (let c = 0; c < 9; c++) {if (b[r][c] === null) {for (let num = 1; num <= 9; num++) {if (isValidForBoard(b, r, c, num)) {b[r][c] = num;dfs();b[r][c] = null;if (count > 1) return;}}return;}}}count++;}dfs();return count;
}// 檢查num能否填入board[row][col](不違反規則)
function isValidForBoard(board, row, col, num) {for (let i = 0; i < 9; i++) {if (board[row][i] === num || board[i][col] === num) return false;}const boxRow = Math.floor(row / 3) * 3;const boxCol = Math.floor(col / 3) * 3;for (let r = 0; r < 3; r++) {for (let c = 0; c < 3; c++) {if (board[boxRow + r][boxCol + c] === num) return false;}}return true;
}// 檢查num能否填入當前棋盤currentBoard[row][col]
function isValidCell(row, col, num) {for (let i = 0; i < 9; i++) {// 同行或同列已存在num,則該格中的數不可用if ((i !== col && currentBoard[row][i] === num) ||(i !== row && currentBoard[i][col] === num)) {return false;}}// 同宮中存在num,則該格中的數不可用const boxRow = Math.floor(row / 3) * 3;const boxCol = Math.floor(col / 3) * 3;for (let r = 0; r < 3; r++) {for (let c = 0; c < 3; c++) {const x = boxRow + r;const y = boxCol + c;if (x !== row && y !== col && currentBoard[x][y] === num) {return false;}}}return true;
}// “查看答案”按鈕事件
// 自動求解當前題目并展示
answerBtn.addEventListener("click", () => {userSteps = Array.from({ length: 9 }, () => Array(9).fill(null));stepCounter = 1;let solution = solveSudoku(JSON.parse(JSON.stringify(originalBoard)));if (!solution) {return;}currentBoard = solution;redCells.clear();timerStart = null;drawBoard();
});// 求解器部分(回溯算法)
// 回溯算法求解數獨,返回解或false
function solveSudoku(board) {function isValid(row, col, num) {for (let i = 0; i < 9; i++) {if (board[row][i] === num || board[i][col] === num) return false;}const boxRow = Math.floor(row / 3) * 3;const boxCol = Math.floor(col / 3) * 3;for (let r = 0; r < 3; r++) {for (let c = 0; c < 3; c++) {if (board[boxRow + r][boxCol + c] === num) return false;}}return true;}function backtrack() {for (let row = 0; row < 9; row++) {for (let col = 0; col < 9; col++) {if (board[row][col] === null) {for (let num = 1; num <= 9; num++) {if (isValid(row, col, num)) {board[row][col] = num;if (backtrack()) return true;board[row][col] = null;}}return false;}}}return true;}if (backtrack()) {return board;}return false;
}// 判斷整個棋盤是否合法(無重復數字)
function isValidSudoku(board) {const rows = Array.from({ length: 9 }, () => new Set());const cols = Array.from({ length: 9 }, () => new Set());const boxes = Array.from({ length: 9 }, () => new Set());for (let r = 0; r < 9; r++) {for (let c = 0; c < 9; c++) {const val = board[r][c];if (val === null) continue;const boxIndex = Math.floor(r / 3) * 3 + Math.floor(c / 3);if (rows[r].has(val) || cols[c].has(val) || boxes[boxIndex].has(val)) {return false;}rows[r].add(val);cols[c].add(val);boxes[boxIndex].add(val);}}return true;
}/*** 指定(row, col)中如果只有唯一可用的數字,將其高亮* @param {number} row 行號(0-8)* @param {number} col 列號(0-8)* @returns {HTMLElement|null} 唯一的可用數字所在 mini-cell元素,否則null*/
// 獲取(row, col)格唯一可填數字的mini-cell元素(若唯一)
function getUniqNumberCell(row, col) {// 獲取對應tdconst tr = boardElement.querySelectorAll('tr')[row];if (!tr) return null;const td = tr.querySelectorAll('td')[col];if (!td) return null;// 找到mini-gridconst miniGrid = td.querySelector('.sudoku-mini-grid');if (!miniGrid) return null;// 找到所有可用數字所在mini-cell。參見drawBoard(),可用數字所在mini-cell類名為blackconst blackCells = miniGrid.querySelectorAll('.mini-cell.black');if (blackCells.length === 1) {return blackCells[0];}return null;
}// 初始化9x9表格
// 初始化自定義題目輸入表格(9x9輸入框)
function initGridTable() {// 清空表格(避免重復添加)inputTtable.innerHTML = '';for (let i = 0; i < 9; i++) {const tr = document.createElement('tr');for (let j = 0; j < 9; j++) {const td = document.createElement('td');const input = document.createElement('input');input.type = 'text';input.classList.add('cell');input.maxLength = 1;// 輸入驗證:當輸入框失去焦點時驗證input.addEventListener('blur', function () {const value = this.value.trim();if (value !== '' && (value < '1' || value > '9')) {alert('請輸入1-9之間的數字!');this.value = '';}});td.appendChild(input);tr.appendChild(td);}inputTtable.appendChild(tr);}
}// 打開對話框
// 打開自定義題目輸入對話框
openBtn.addEventListener('click', () => {// 初始化表格(如果還沒有初始化的話)initGridTable();inputDialog.showModal();
});// 提交按鈕點擊事件,將輸入的數據保存到originalBoard數組中
// 提交自定義題目,校驗唯一解并作為新題載入
submitBtn.addEventListener('click', () => {let boardconst rows = inputTtable.querySelectorAll('tr');for (let i = 0; i < rows.length; i++) {const cells = rows[i].querySelectorAll('input');for (let j = 0; j < cells.length; j++) {const value = cells[j].value.trim();currentBoard[i][j] = (value === '' ? null : parseInt(value, 10));}}// 關閉輸入題目對話框inputDialog.close();if (isValidSudoku(currentBoard)) {if (countSolutions(currentBoard) === 1) {// 所出的題有且只有唯一解,初始化相關變量,開始新游戲originalBoard = JSON.parse(JSON.stringify(currentBoard));userSteps = Array.from({ length: 9 }, () => Array(9).fill(null));stepCounter = 1;redCells.clear();timerStart = null;timerUsed = 0;// 重繪盤面drawBoard();} else {  // 無解或有多個解,返回原盤面alert('此題不是有且只有唯一解');return;}} else {  // 出的題中同行、同列或同宮有相同數字,返回原盤面alert('此題不合法');return;}
});drawBoard();

本文代碼在CSDN的C知道生成的單道數獨題做題界面的代碼框架基礎上改進和增加功能而成。

使用技巧:

1、先把所有只有單個數字可用的單元格全部確定;

2、利用所掌握的技巧將所有可確定數字的單元格全部確定,技巧掌握得少不要緊,不用技巧都可以,不過花的時間稍多;

3、重復1,2,直至再沒有可確定的單元格,然后選擇只有兩個數字的單元格,隨便選一個數,然后再點擊讓它變紅,然后又重復1、2步;

4、出現某個單元格無數字可填的時候,撤銷到上一步標記紅色的單元格中的步數,然后換該單元格中的另一個數,這次不用標記紅色了,然后重復1,2,3。如果所有單元格都至少有兩個數字可填,按步驟3的做法在只有兩個候選數的單元格中隨便選一個,標記紅色,然后重復1、2步。出現第4步第一種情形,撤銷到后一個紅色標記單元格中的步數,再重復1,2,3,4步,直至完成。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/88365.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/88365.shtml
英文地址,請注明出處:http://en.pswp.cn/web/88365.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

嵌入式硬件中電容的基本原理與實現詳解02

我們今天重點討論點知識點如下: 1.各種種類的電容優缺點對比講解 2.電容的標稱值介紹 3.電容的單位介紹 4.常見的電壓信號有哪些? 5. 電容的耐壓值講解 6.電容的容值有哪些? 7.12pF、15pF 電容常用在什么場合? 8. 振蕩電路中使用的電容常常需要使用什么材質的電容? 9.100n…

Python訓練打卡DAY46

DAY46&#xff1a;通道注意力&#xff08;SE注意力&#xff09; 恩師浙大疏錦行 知識點&#xff1a; 不同CNN層的特征圖&#xff1a;不同通道的特征圖什么是注意力&#xff1a;注意力家族&#xff0c;類似于動物園&#xff0c;都是不同的模塊&#xff0c;好不好試了才知道。通…

fastadmin_php專項

1.時間的判斷,還有就是在php這邊如何去拿前端html元素上面的值input($row.borrowtime);// 創建兩個 DateTime 對象$row_expecttime new \DateTime(input($row.borrowtime));$par_expecttime new \DateTime( $params[expecttime]); // // 計算兩個日期之間的差異 // …

如何在MySQL中選擇使用InnoDB還是MyISAM引擎?

在 MySQL 中選擇 InnoDB 還是 MyISAM 存儲引擎時&#xff0c;需根據應用場景的需求權衡功能、性能和數據完整性。以下是具體的選擇指南&#xff1a; 1. 優先考慮事務和外鍵需求必須使用 InnoDB&#xff1a; 若應用需要 事務支持&#xff08;如金融轉賬、訂單處理&#xff09;或…

邀請函 | 知從科技邀您共赴2025 RISC-V 中國峰會

第五屆RISC-V中國峰會將于2025年7月16至19日在上海張江科學會堂隆重舉辦&#xff0c;本屆峰會由上海開放處理器產業創新中心&#xff08;SOPIC&#xff09;主辦&#xff0c;RISC-V國際開源實驗室&#xff08;RIOS實驗室&#xff09;和上海張江高科技園區開發股份有限公司聯合主…

企業數字化轉型規劃和建設方案(管理架構、應用架構、技術架構)PPT

一、戰略定位與核心目標以 “技術賦能業務&#xff0c;數據驅動創新” 為核心思路&#xff0c;構建 “三步走” 戰略演進路徑&#xff0c;實現 IT 從 “基礎支撐” 到 “戰略引擎” 的升級&#xff1a;IT1.0&#xff08;1-2 年&#xff09;&#xff1a;夯實基礎能力定位 “穩健…

基于Uniapp+MySQL+PHP的景區多商戶小程序源碼系統 帶完整的搭建指南

溫馨提示&#xff1a;文末有資源獲取方式該系統采用 PHP MySQL 的經典開發組合。PHP 作為一種廣泛使用的開源腳本語言&#xff0c;具有簡單易學、運行速度快、跨平臺性強等優點&#xff0c;能夠快速開發出功能強大的 Web 應用程序。MySQL 則是一款穩定可靠的關系型數據庫管理系…

阿里云和騰訊云RocketMQ 發消息和消費消息客戶端JAVA接口

一、RocketMQ 概述RocketMQ 是阿里巴巴開源的一款分布式消息中間件&#xff0c;后捐贈給 Apache 基金會成為頂級項目。它具有低延遲、高并發、高可用、高可靠等特點&#xff0c;廣泛應用于訂單交易、消息推送、流計算、日志收集等場景。核心特點分布式架構&#xff1a;支持集群…

Vue響應式原理六:Vue3響應式原理

1. 多個對象響應式當前存在的問題&#xff1a;當前實現僅針對某個固定對象&#xff08;obj&#xff09;進行依賴收集&#xff0c;實際開發中需要處理多個不同對象將對象響應式處理邏輯抽取為通用函數&#xff0c;支持任意對象代碼如下&#xff1a; // 方案一&#xff1a;Obje…

【算法筆記 day three】滑動窗口(其他類型)

hello大家好&#xff01;這份筆記包含的題目類型主要包括求子數組已經一些比較‘小眾’的題目。和之前一樣&#xff0c;筆記中的代碼和思路要么是我手搓要么是我借鑒一些大佬的想法轉化成自己的話復現。所以方法不一定是最好的&#xff0c;但一定是經過我理解的產物&#xff0c…

docker-鏡像管理指南

在本節中&#xff0c;我們將詳細介紹 Docker 鏡像的常用命令&#xff0c;幫助您更好地管理和操作鏡像。以下是核心命令及其功能說明&#xff1a;1.使用"ls"查看鏡像列表#查看現有的鏡像列表[rootdocker01 ~]# docker images [rootdocker01 ~]# docker image ls#僅查看…

Mac 電腦無法讀取硬盤的解決方案

引言近年來&#xff0c;選擇使用 Mac 電腦的用戶越來越多&#xff0c;尤其是在設計、開發、剪輯、文檔處理等領域&#xff0c;macOS 憑借其優秀的系統生態與硬件體驗吸引了大量擁躉。與此同時&#xff0c;對于攝影師、剪輯師、程序員、學生等用戶來說&#xff0c;一塊移動硬盤往…

25春期末考

web 瘋狂星期四 先來看一下源碼 分析代碼的黑名單后得知 我們可以用的字符就只剩下 字母a-z(大小寫均可) 數字2 空格 這里的限制太多了 這里比較常用的getallheaders被ban掉了 這里就是用session來做 session_start()開啟session session_id()獲取session 這里我們要構造一…

時間顯示 藍橋云課Java

目錄 題目鏈接 題目 解題思路 代碼 題目鏈接 競賽中心 - 藍橋云課 題目 解題思路 通過%天數,得到一天內的時間,然后/小時單位(換算成毫秒的)得到小時,然后總數減去該小時,得到分鐘數,秒數同理 代碼 import java.util.Scanner; // 1:無需package // 2: 類名必須Main, 不…

STM32F1控制步進電機

一、基礎知識1. 步進電機控制方式脈沖方向控制&#xff08;最常見&#xff09;控制信號&#xff1a;DIR方向&#xff1a;高低電平決定正轉或反轉&#xff1b;STEP脈沖&#xff1a;每個脈沖電機前進一步&#xff08;可通過端口拉高拉低來模擬脈沖&#xff0c;或使用pwm來生成脈沖…

Docker 容器部署腳本

#!/bin/bash# # Author: ldj # Date: 2025-07-08 15:37:11 # Description: 首先刪除舊的容器和鏡像&#xff0c;然后登錄到 Harbor 并拉取最新的鏡像進行部署 # # 顯示每條命令執行情況&#xff0c;便于調試 set -x harbor_addr$1 harbor_repo$2 project_name$3 version$4 po…

OpenCV 4.10.0 移植 - Android

前文: Ubuntu 編譯 OpenCV SDK for Android Linux OpenCV 4.10.0 移植 概述 在移動應用開發領域&#xff0c;Android平臺與OpenCV庫的結合為開發者提供了強大的圖像處理和計算機視覺能力。OpenCV(Open Source Computer Vision Library)是一個開源的計算機視覺和機器學習軟件…

go go go 出發咯 - go web開發入門系列(二) Gin 框架實戰指南

go go go 出發咯 - go web開發入門系列&#xff08;二&#xff09; Gin 框架實戰指南 往期回顧 go go go 出發咯 - go web開發入門系列&#xff08;一&#xff09; helloworld 前言 前一節我們使用了go語言簡單的通過net/http搭建了go web服務&#xff0c;但是僅使用 Go 的標…

編譯OpenHarmony-4.0-Release RK3566 報錯

編譯OpenHarmony-4.0-Release RK3566 報錯1. 報錯問題2.問題解決3.解決方案4.?調試技巧?subsystem name config incorrect in ‘/home/openharmony/OpenHarmony/vendor/kaihong/khdvk_356b/bundle.json’, build file subsystem name is kaihong_products,configured subsy1.…

【PTA數據結構 | C語言版】線性表循環右移

本專欄持續輸出數據結構題目集&#xff0c;歡迎訂閱。 文章目錄題目代碼題目 給定順序表 A(a1?,a2?,?,an?)&#xff0c;請設計一個時間和空間上盡可能高效的算法將該線性表循環右移指定的 m 位。例如&#xff0c;(1,2,5,7,3,4,6,8) 循環右移 3 位&#xff08;m3) 后的結果…