PageSourceSearch

https://fangg.netlify.app/js/apps/minesweeper.js

js fangg.netlify.app collected 2026-10-03 10:43:32 UTC 11,613 bytes, 371 lines download raw bytes

1// 扫雷游戏实现
2class Minesweeper {
3    constructor() {
4        this.BOARD_SIZE = 9;
5        this.MINES_COUNT = 10;
6        this.board = [];
7        this.revealed = [];
8        this.flagged = [];
9        this.gameOver = false;
10        this.firstClick = true;
11        this.timeElapsed = 0;
12        this.timer = null;
13        this.minesLeft = this.MINES_COUNT;
14        
15        this.container = document.getElementById('minesweeper-container');
16        this.init();
17    }
18
19    init() {
20        // 创建游戏控制面板
21        const controlPanel = document.createElement('div');
22        controlPanel.className = 'control-panel';
23        controlPanel.innerHTML = `
24            <div class="mine-counter">${String(this.minesLeft).padStart(3, '0')}</div>
25            <button class="reset-button">😊</button>
26            <div class="timer">000</div>
27        `;
28        this.container.appendChild(controlPanel);
29
30        // 创建游戏棋盘
31        const gameBoard = document.createElement('div');
32        gameBoard.className = 'game-board';
33        this.container.appendChild(gameBoard);
34
35        // 初始化游戏数据
36        this.initializeBoard();
37        this.renderBoard();
38
39        // 添加事件监听器
40        this.addEventListeners();
41
42        // 添加样式
43        this.addStyles();
44    }
45
46    addStyles() {
47        const style = document.createElement('style');
48        style.textContent = `
49            #minesweeper-container {
50                padding: 10px;
51                background: #c0c0c0;
52                border: 2px solid #808080;
53            }
54            .control-panel {
55                display: flex;
56                justify-content: space-between;
57                margin-bottom: 10px;
58                padding: 5px;
59                background: #c0c0c0;
60                border: 2px solid #808080;
61            }
62            .mine-counter, .timer {
63                background: #000;
64                color: #f00;
65                padding: 2px 5px;
66                font-family: 'Digital', monospace;
67                font-size: 20px;
68            }
69            .reset-button {
70                width: 26px;
71                height: 26px;
72                font-size: 20px;
73                border: 2px solid #fff;
74                border-right-color: #808080;
75                border-bottom-color: #808080;
76                background: #c0c0c0;
77                cursor: pointer;
78                display: flex;
79                align-items: center;
80                justify-content: center;
81                padding: 0;
82            }
83            .game-board {
84                display: grid;
85                grid-template-columns: repeat(${this.BOARD_SIZE}, 30px);
86                gap: 1px;
87                background: #808080;
88                border: 3px solid #808080;
89            }
90            .cell {
91                width: 30px;
92                height: 30px;
93                background: #c0c0c0;
94                border: 2px solid #fff;
95                border-right-color: #808080;
96                border-bottom-color: #808080;
97                display: flex;
98                align-items: center;
99                justify-content: center;
100                font-weight: bold;
101                cursor: pointer;
102                font-size: 16px;
103            }
104            .cell.revealed {
105                border: 1px solid #808080;
106                background: #c0c0c0;
107            }
108            .cell[data-number="1"] { color: blue; }
109            .cell[data-number="2"] { color: green; }
110            .cell[data-number="3"] { color: red; }
111            .cell[data-number="4"] { color: darkblue; }
112            .cell[data-number="5"] { color: darkred; }
113            .cell[data-number="6"] { color: teal; }
114            .cell[data-number="7"] { color: black; }
115            .cell[data-number="8"] { color: gray; }
116        `;
117        document.head.appendChild(style);
118    }
119
120    initializeBoard() {
121        // 初始化空棋盘
122        this.board = Array(this.BOARD_SIZE).fill().map(() => 
123            Array(this.BOARD_SIZE).fill(0)
124        );
125        this.revealed = Array(this.BOARD_SIZE).fill().map(() => 
126            Array(this.BOARD_SIZE).fill(false)
127        );
128        this.flagged = Array(this.BOARD_SIZE).fill().map(() => 
129            Array(this.BOARD_SIZE).fill(false)
130        );
131    }
132
133    placeMines(firstX, firstY) {
134        let minesPlaced = 0;
135        while (minesPlaced < this.MINES_COUNT) {
136            const x = Math.floor(Math.random() * this.BOARD_SIZE);
137            const y = Math.floor(Math.random() * this.BOARD_SIZE);
138            
139            // 确保不在第一次点击的位置及其周围放置地雷
140            if (this.board[x][y] !== -1 && 
141                (Math.abs(x - firstX) > 1 || Math.abs(y - firstY) > 1)) {
142                this.board[x][y] = -1;
143                minesPlaced++;
144            }
145        }
146
147        // 计算每个格子周围的地雷数
148        for (let x = 0; x < this.BOARD_SIZE; x++) {
149            for (let y = 0; y < this.BOARD_SIZE; y++) {
150                if (this.board[x][y] !== -1) {
151                    this.board[x][y] = this.countAdjacentMines(x, y);
152                }
153            }
154        }
155    }
156
157    countAdjacentMines(x, y) {
158        let count = 0;
159        for (let dx = -1; dx <= 1; dx++) {
160            for (let dy = -1; dy <= 1; dy++) {
161                const newX = x + dx;
162                const newY = y + dy;
163                if (newX >= 0 && newX < this.BOARD_SIZE && 
164                    newY >= 0 && newY < this.BOARD_SIZE && 
165                    this.board[newX][newY] === -1) {
166                    count++;
167                }
168            }
169        }
170        return count;
171    }
172
173    renderBoard() {
174        const gameBoard = this.container.querySelector('.game-board');
175        gameBoard.innerHTML = '';
176
177        for (let x = 0; x < this.BOARD_SIZE; x++) {
178            for (let y = 0; y < this.BOARD_SIZE; y++) {
179                const cell = document.createElement('div');
180                cell.className = 'cell';
181                cell.dataset.x = x;
182                cell.dataset.y = y;
183
184                if (this.revealed[x][y]) {
185                    cell.classList.add('revealed');
186                    if (this.board[x][y] === -1) {
187                        cell.textContent = '💣';
188                    } else if (this.board[x][y] > 0) {
189                        cell.textContent = this.board[x][y];
190                        cell.dataset.number = this.board[x][y];
191                    }
192                } else if (this.flagged[x][y]) {
193                    cell.textContent = '🚩';
194                }
195
196                gameBoard.appendChild(cell);
197            }
198        }
199    }
200
201    addEventListeners() {
202        const gameBoard = this.container.querySelector('.game-board');
203        const resetButton = this.container.querySelector('.reset-button');
204
205        gameBoard.addEventListener('click', (e) => {
206            const cell = e.target.closest('.cell');
207            if (cell && !this.gameOver) {
208                const x = parseInt(cell.dataset.x);
209                const y = parseInt(cell.dataset.y);
210                this.handleClick(x, y);
211            }
212        });
213
214        gameBoard.addEventListener('contextmenu', (e) => {
215            e.preventDefault();
216            const cell = e.target.closest('.cell');
217            if (cell && !this.gameOver) {
218                const x = parseInt(cell.dataset.x);
219                const y = parseInt(cell.dataset.y);
220                this.handleRightClick(x, y);
221            }
222        });
223
224        resetButton.addEventListener('click', () => {
225            this.resetGame();
226        });
227    }
228
229    handleClick(x, y) {
230        if (this.flagged[x][y] || this.revealed[x][y]) return;
231
232        if (this.firstClick) {
233            this.firstClick = false;
234            this.placeMines(x, y);
235            this.startTimer();
236        }
237
238        if (this.board[x][y] === -1) {
239            this.gameOver = true;
240            this.revealAllMines();
241            this.updateResetButton('😵');
242            this.stopTimer();
243            return;
244        }
245
246        this.reveal(x, y);
247        this.renderBoard();
248
249        if (this.checkWin()) {
250            this.gameOver = true;
251            this.updateResetButton('😎');
252            this.stopTimer();
253        }
254    }
255
256    handleRightClick(x, y) {
257        if (this.revealed[x][y]) return;
258
259        this.flagged[x][y] = !this.flagged[x][y];
260        this.minesLeft += this.flagged[x][y] ? -1 : 1;
261        this.updateMineCounter();
262        this.renderBoard();
263
264        if (this.checkWin()) {
265            this.gameOver = true;
266            this.updateResetButton('😎');
267            this.stopTimer();
268        }
269    }
270
271    reveal(x, y) {
272        if (x < 0 || x >= this.BOARD_SIZE || y < 0 || y >= this.BOARD_SIZE || 
273            this.revealed[x][y] || this.flagged[x][y]) {
274            return;
275        }
276
277        this.revealed[x][y] = true;
278
279        if (this.board[x][y] === 0) {
280            for (let dx = -1; dx <= 1; dx++) {
281                for (let dy = -1; dy <= 1; dy++) {
282                    this.reveal(x + dx, y + dy);
283                }
284            }
285        }
286    }
287
288    revealAllMines() {
289        for (let x = 0; x < this.BOARD_SIZE; x++) {
290            for (let y = 0; y < this.BOARD_SIZE; y++) {
291                if (this.board[x][y] === -1) {
292                    this.revealed[x][y] = true;
293                }
294            }
295        }
296        this.renderBoard();
297    }
298
299    checkWin() {
300        for (let x = 0; x < this.BOARD_SIZE; x++) {
301            for (let y = 0; y < this.BOARD_SIZE; y++) {
302                if (this.board[x][y] !== -1 && !this.revealed[x][y]) {
303                    return false;
304                }
305                if (this.board[x][y] === -1 && !this.flagged[x][y]) {
306                    return false;
307                }
308            }
309        }
310        return true;
311    }
312
313    updateResetButton(emoji) {
314        const resetButton = this.container.querySelector('.reset-button');
315        resetButton.textContent = emoji;
316    }
317
318    updateMineCounter() {
319        const counter = this.container.querySelector('.mine-counter');
320        counter.textContent = String(this.minesLeft).padStart(3, '0');
321    }
322
323    startTimer() {
324        const timerDisplay = this.container.querySelector('.timer');
325        this.timer = setInterval(() => {
326            this.timeElapsed++;
327            timerDisplay.textContent = String(this.timeElapsed).padStart(3, '0');
328        }, 1000);
329    }
330
331    stopTimer() {
332        if (this.timer) {
333            clearInterval(this.timer);
334            this.timer = null;
335        }
336    }
337
338    resetGame() {
339        this.stopTimer();
340        this.timeElapsed = 0;
341        this.minesLeft = this.MINES_COUNT;
342        this.gameOver = false;
343        this.firstClick = true;
344        this.initializeBoard();
345        this.updateResetButton('😊');
346        this.updateMineCounter();
347        this.container.querySelector('.timer').textContent = '000';
348        this.renderBoard();
349    }
350}
351
352// 显示关于弹窗
353function showMinesweeperAbout() {
354    const aboutModal = document.getElementById('minesweeper-about');
355    aboutModal.style.display = 'block';
356}
357
358// 隐藏关于弹窗
359function hideMinesweeperAbout() {
360    const aboutModal = document.getElementById('minesweeper-about');
361    aboutModal.style.display = 'none';
362}
363
364// 当窗口打开时初始化游戏
365document.addEventListener('DOMContentLoaded', () => {
366    const minesweeperWindow = document.getElementById('minesweeper-window');
367    if (!minesweeperWindow.dataset.initialized) {
368        new Minesweeper();
369        minesweeperWindow.dataset.initialized = 'true';
370    }
371});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.