Merge branch 'master' of https://github.com/Angels-dev/MultiMinesweeper-Game
This commit is contained in:
3
package-lock.json
generated
3
package-lock.json
generated
@@ -25,7 +25,7 @@
|
|||||||
"npm-run-all2": "^6.2.3",
|
"npm-run-all2": "^6.2.3",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "~5.5.4",
|
"typescript": "~5.5.4",
|
||||||
"vite": "^5.4.8",
|
"vite": "^5.4.10",
|
||||||
"vite-plugin-vue-devtools": "^7.4.6",
|
"vite-plugin-vue-devtools": "^7.4.6",
|
||||||
"vue-tsc": "^2.1.6"
|
"vue-tsc": "^2.1.6"
|
||||||
}
|
}
|
||||||
@@ -4553,7 +4553,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
|
||||||
"integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
|
"integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.43",
|
"postcss": "^8.4.43",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"npm-run-all2": "^6.2.3",
|
"npm-run-all2": "^6.2.3",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "~5.5.4",
|
"typescript": "~5.5.4",
|
||||||
"vite": "^5.4.8",
|
"vite": "^5.4.10",
|
||||||
"vite-plugin-vue-devtools": "^7.4.6",
|
"vite-plugin-vue-devtools": "^7.4.6",
|
||||||
"vue-tsc": "^2.1.6"
|
"vue-tsc": "^2.1.6"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 56 KiB |
39
src/App.vue
39
src/App.vue
@@ -24,42 +24,3 @@
|
|||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
header {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 2;
|
|
||||||
height: 40px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #333;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
header * {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
left: 0;
|
|
||||||
width: 200px;
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: left;
|
|
||||||
left: 220px;
|
|
||||||
font-size: medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
|
||||||
right: 100px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav * {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -45,38 +45,197 @@ a,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* APP HEADER */
|
||||||
|
header {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 2;
|
||||||
|
height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
* {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
left: 0;
|
||||||
|
width: 200px;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
text-align: left;
|
||||||
|
left: 220px;
|
||||||
|
font-size: medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
right: 100px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav * {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* SIDEBAR NAVIGATION */
|
/* SIDEBAR NAVIGATION */
|
||||||
.sidenav {
|
.sidenav {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
width: 203px; /* 200px is the width of the header vl position */
|
width: 203px; /* 200px is the width of the header vl position + half the bar */
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
}
|
|
||||||
|
|
||||||
.sidenav a {
|
a {
|
||||||
padding: 6px 8px 6px 16px;
|
padding: 6px 8px 6px 16px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
color: #818181;
|
color: #818181;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
|
||||||
|
|
||||||
.sidenav a:hover {
|
}
|
||||||
color: #f1f1f1;
|
|
||||||
|
a:hover {
|
||||||
|
color: #f1f1f1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* PAGE CONTENT */
|
/* PAGE CONTENT */
|
||||||
.main {
|
.main {
|
||||||
margin-top: 40px; /* 60px is the height of the header navbar */
|
|
||||||
height: calc(100% - 40px);
|
|
||||||
padding: 2rem 2rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 10px;
|
align-items: center;
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 40px);
|
||||||
|
margin-top: 40px; /* 40px is the height of the header navbar */
|
||||||
|
padding: 2rem;
|
||||||
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* GAME BOARD */
|
||||||
|
.space {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end; /* Centre les deux éléments horizontalement */
|
||||||
|
width: 100%;
|
||||||
|
gap: 20px; /* Ajoute un espacement entre le h1 et le timer */
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer {
|
||||||
|
text-align: center;
|
||||||
|
width: 135px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid #555;
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between; /* Aligne les deux groupes à gauche et à droite */
|
||||||
|
width: 100%; /* Prend toute la largeur disponible */
|
||||||
|
margin-top: -5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 36px;
|
||||||
|
color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.presets {
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px; /* Ajoute un espacement entre les boutons */
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 75px;
|
||||||
|
height: 50px;
|
||||||
|
background-color: #3f77ff;
|
||||||
|
border: 2px solid #7aa0ff;
|
||||||
|
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #1c5dff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px; /* Ajoute un espacement entre les inputs */
|
||||||
|
margin-right: 20px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
text-align: center;
|
||||||
|
width: 80px;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.newgame {
|
||||||
|
width: 135px;
|
||||||
|
margin-top: 15px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
border: 2px solid #6bdf6f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newgame:hover {
|
||||||
|
background-color: #3b8a3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
border: 4px solid rgb(65, 65, 65);
|
||||||
|
margin: 0 auto; /* Centre la grille horizontalement */
|
||||||
|
overflow-x: auto; /* Active le défilement horizontal si nécessaire */
|
||||||
|
max-width: 100%; /* Empêche la grille de déborder trop */
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
text-align: center;
|
||||||
|
color: black;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FORMATTING AND EFFECTS */
|
||||||
.rounded {
|
.rounded {
|
||||||
border-radius: 20%;
|
border-radius: 20%;
|
||||||
}
|
}
|
||||||
@@ -99,6 +258,20 @@ a,
|
|||||||
border-left: 6px solid rgb(0, 255, 140);
|
border-left: 6px solid rgb(0, 255, 140);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
transform: translate(-50%, 50%);
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ROUTE HIGHLIGHT */
|
/* ROUTE HIGHLIGHT */
|
||||||
.router-link-exact-active {
|
.router-link-exact-active {
|
||||||
border-left: 5px solid rgb(0, 255, 140);
|
border-left: 5px solid rgb(0, 255, 140);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
type CellGrid = string[][] // type CellGrid (grille de jeu)
|
type CellGrid = string[][] // type CellGrid (grille de jeu)
|
||||||
type MineGrid = number[][] // type MineGrid (grille de bombes)
|
type MineGrid = number[][] // type MineGrid (grille de bombes)
|
||||||
type Directions = number[][] // type Directions (directions possibles)
|
type Directions = number[][] // type Directions (directions possibles)
|
||||||
|
|
||||||
function getDirections(x: number, y: number): Directions {
|
function getDirections(x: number, y: number): Directions {
|
||||||
return [[x-1, y-1], [x-1, y], [x-1, y+1], [x, y-1], [x, y+1], [x+1, y-1], [x+1, y], [x+1, y+1]]
|
return [[x-1, y-1], [x-1, y], [x-1, y+1], [x, y-1], [x, y+1], [x+1, y-1], [x+1, y], [x+1, y+1]]
|
||||||
@@ -27,14 +27,23 @@ function genMineGrid(width: number, length: number, nbMines: number, fx: number,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function cliqueGauche(gameStatus: number, cellGrid: CellGrid, mineGrid: MineGrid, x: number, y: number) {
|
function cliqueGauche(cellGrid: CellGrid, mineGrid: MineGrid, x: number, y: number): number {
|
||||||
const checkDrop = ['/sEmpty.png', '/sUnknown.png', '/sFlag.png', '/sQuestion.png']
|
const checkDrop = ['/sEmpty.png', '/sUnknown.png', '/sFlag.png', '/sQuestion.png']
|
||||||
if (checkDrop.includes(cellGrid[y][x])) return
|
if (checkDrop.includes(cellGrid[y][x])) return 1
|
||||||
else if (cellGrid[y][x] === '/sClick.png') checkCell(gameStatus, cellGrid, mineGrid, x, y)
|
else if (cellGrid[y][x] === '/sClick.png') {
|
||||||
else revealCell(gameStatus, cellGrid, mineGrid, x, y)
|
const result = checkCell(cellGrid, mineGrid, x, y)
|
||||||
|
if (result !== 0) return result
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const result = revealCell(cellGrid, mineGrid, x, y)
|
||||||
|
if (result !== 0) return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continuer le jeu
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function cliqueDroit(cellGrid: string[][], x: number, y: number) { // fonction cliqueDroit (flag d'une case) // modification de l'image de la case
|
function cliqueDroit(cellGrid: string[][], x: number, y: number) { // fonction cliqueDroit (flag d'une case)
|
||||||
switch (cellGrid[y][x]) {
|
switch (cellGrid[y][x]) {
|
||||||
case '/sUnknown.png':
|
case '/sUnknown.png':
|
||||||
cellGrid[y][x] = "/sFlag.png"
|
cellGrid[y][x] = "/sFlag.png"
|
||||||
@@ -48,9 +57,10 @@ function cliqueDroit(cellGrid: string[][], x: number, y: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkCell(gameStatus: number, cellGrid: CellGrid, mineGrid: MineGrid, x: number, y: number) {
|
function checkCell(cellGrid: CellGrid, mineGrid: MineGrid, x: number, y: number): number {
|
||||||
if (mineGrid.some(m => m[0] === x && m[1] === y)) return gameOver(gameStatus, cellGrid, mineGrid, x, y)
|
if (mineGrid.some(m => m[0] === x && m[1] === y)) return gameOver(cellGrid, mineGrid, x, y)
|
||||||
|
|
||||||
|
// Compter le nombre de mines adjacentes
|
||||||
let minesAdjacentes = 0
|
let minesAdjacentes = 0
|
||||||
const directions = getDirections(x, y)
|
const directions = getDirections(x, y)
|
||||||
for (const [dx, dy] of directions) {
|
for (const [dx, dy] of directions) {
|
||||||
@@ -58,18 +68,34 @@ function checkCell(gameStatus: number, cellGrid: CellGrid, mineGrid: MineGrid, x
|
|||||||
if (mineGrid.some(m => m[0] === dx && m[1] === dy)) minesAdjacentes++
|
if (mineGrid.some(m => m[0] === dx && m[1] === dy)) minesAdjacentes++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minesAdjacentes > 0) cellGrid[y][x] = `/s${minesAdjacentes}.png`
|
// Si la cellule a des mines adjacentes, afficher le nombre de mines
|
||||||
else {
|
if (minesAdjacentes > 0) {
|
||||||
|
cellGrid[y][x] = `/s${minesAdjacentes}.png`
|
||||||
|
|
||||||
|
const result = revealCell(cellGrid, mineGrid, x, y)
|
||||||
|
if (result !== 0) return result
|
||||||
|
} else {
|
||||||
cellGrid[y][x] = '/sEmpty.png'
|
cellGrid[y][x] = '/sEmpty.png'
|
||||||
for (const [dx, dy] of directions) {
|
for (const [dx, dy] of directions) {
|
||||||
if (dx >= 0 && dx < cellGrid[0].length && dy >= 0 && dy < cellGrid.length && cellGrid[dy][dx] === '/sUnknown.png') {
|
if (dx >= 0 && dx < cellGrid[0].length && dy >= 0 && dy < cellGrid.length && cellGrid[dy][dx] === '/sUnknown.png') {
|
||||||
checkCell(gameStatus, cellGrid, mineGrid, dx, dy)
|
const result = checkCell(cellGrid, mineGrid, dx, dy)
|
||||||
|
if (result !== 0) return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vérifier si toutes les cellules non-mines sont révélées pour déterminer la victoire
|
||||||
|
if (cellGrid.flat().every((cell, index) => {
|
||||||
|
const x = index % cellGrid[0].length
|
||||||
|
const y = Math.floor(index / cellGrid[0].length)
|
||||||
|
return cell !== '/sUnknown.png' || mineGrid.some(m => m[0] === x && m[1] === y)
|
||||||
|
})) return 2
|
||||||
|
|
||||||
|
// Continuer le jeu
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function revealCell(gameStatus: number, cellGrid: CellGrid, mineGrid: MineGrid, x: number, y: number) {
|
function revealCell(cellGrid: CellGrid, mineGrid: MineGrid, x: number, y: number): number {
|
||||||
const directions = getDirections(x, y)
|
const directions = getDirections(x, y)
|
||||||
let flagCount = 0
|
let flagCount = 0
|
||||||
for (const [dx, dy] of directions) {
|
for (const [dx, dy] of directions) {
|
||||||
@@ -78,18 +104,24 @@ function revealCell(gameStatus: number, cellGrid: CellGrid, mineGrid: MineGrid,
|
|||||||
const cellValue = parseInt(cellGrid[y][x].match(/\/s(\d)\.png$/)?.[1] || '0')
|
const cellValue = parseInt(cellGrid[y][x].match(/\/s(\d)\.png$/)?.[1] || '0')
|
||||||
if (flagCount === cellValue) {
|
if (flagCount === cellValue) {
|
||||||
for (const [dx, dy] of directions) {
|
for (const [dx, dy] of directions) {
|
||||||
if (dx >= 0 && dx < cellGrid[0].length && dy >= 0 && dy < cellGrid.length && cellGrid[dy][dx] === '/sHighlight.png') {
|
if (dx >= 0 && dx < cellGrid[0].length && dy >= 0 && dy < cellGrid.length && (cellGrid[dy][dx] === '/sHighlight.png' || cellGrid[dy][dx] === '/sUnknown.png')) {
|
||||||
cellGrid[dy][dx] = '/sUnknown.png'
|
cellGrid[dy][dx] = '/sUnknown.png'
|
||||||
checkCell(gameStatus, cellGrid, mineGrid, dx, dy)
|
|
||||||
|
const result = checkCell(cellGrid, mineGrid, dx, dy)
|
||||||
|
if (result !== 0) return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Continuer le jeu
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function gameOver(gameStatus: number, cellGrid: CellGrid, mineGrid: MineGrid, x: number, y: number) {
|
function gameOver(cellGrid: CellGrid, mineGrid: MineGrid, x: number, y: number): number {
|
||||||
for (const [mx, my] of mineGrid) {
|
for (const [mx, my] of mineGrid) {
|
||||||
if (cellGrid[my][mx] === '/sUnknown.png') cellGrid[my][mx] = '/sMine.png'
|
if (cellGrid[my][mx] === '/sUnknown.png') cellGrid[my][mx] = '/sMine.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier l'emplacement de tous les flags dans toute la grille et les remplacer par '/sFlagWrong.png' s'il y en a un qui n'est pas sur une bombe
|
// Vérifier l'emplacement de tous les flags dans toute la grille et les remplacer par '/sFlagWrong.png' s'il y en a un qui n'est pas sur une bombe
|
||||||
for (let y = 0; y < cellGrid.length; y++) {
|
for (let y = 0; y < cellGrid.length; y++) {
|
||||||
for (let x = 0; x < cellGrid[y].length; x++) {
|
for (let x = 0; x < cellGrid[y].length; x++) {
|
||||||
@@ -98,8 +130,8 @@ function gameOver(gameStatus: number, cellGrid: CellGrid, mineGrid: MineGrid, x:
|
|||||||
}
|
}
|
||||||
|
|
||||||
cellGrid[y][x] = '/sExploded.png'
|
cellGrid[y][x] = '/sExploded.png'
|
||||||
gameStatus = 3
|
return 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getDirections, genCellGrid, genMineGrid, cliqueGauche, cliqueDroit } // export des fonctions
|
export { getDirections, genCellGrid, genMineGrid, cliqueGauche, cliqueDroit } // export des fonctions
|
||||||
export type { Directions, CellGrid, MineGrid } // export des types
|
export type { Directions, CellGrid, MineGrid } // export des types
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<h1>Multijoueur</h1>
|
<h1>Multijoueur</h1>
|
||||||
|
<p>Work in progress...</p>
|
||||||
|
<p>Vous pouvez jouer en solo pour l'instant !</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,13 +4,13 @@ import { getDirections, genCellGrid, genMineGrid, cliqueGauche, cliqueDroit } fr
|
|||||||
import type { CellGrid, MineGrid } from '@/utils/game'
|
import type { CellGrid, MineGrid } from '@/utils/game'
|
||||||
|
|
||||||
// Variables du jeu
|
// Variables du jeu
|
||||||
const width = 10
|
const width = ref(10)
|
||||||
const length = 15
|
const length = ref(15)
|
||||||
const nbMines = 25
|
const nbMines = ref(20)
|
||||||
|
|
||||||
// Création grille des cases
|
// Création grille des cases
|
||||||
const cellGrid = ref<CellGrid>([])
|
const cellGrid = ref<CellGrid>([])
|
||||||
cellGrid.value = genCellGrid(width, length)
|
cellGrid.value = genCellGrid(width.value, length.value)
|
||||||
|
|
||||||
// Création grille des mines
|
// Création grille des mines
|
||||||
let mineGrid: MineGrid = []
|
let mineGrid: MineGrid = []
|
||||||
@@ -18,18 +18,45 @@ let mineGrid: MineGrid = []
|
|||||||
// État du jeu
|
// État du jeu
|
||||||
const gameStatus = ref(0) // 0: vide, 1: en cours, 2: gagné, 3: perdu
|
const gameStatus = ref(0) // 0: vide, 1: en cours, 2: gagné, 3: perdu
|
||||||
const timer = ref(0)
|
const timer = ref(0)
|
||||||
let timerInterval: number | undefined = undefined
|
let timerInterval: number | null = null
|
||||||
|
|
||||||
|
// Fonction pour démarrer une nouvelle partie
|
||||||
|
const startNewGame = () => {
|
||||||
|
gameStatus.value = 0
|
||||||
|
timer.value = 0
|
||||||
|
cellGrid.value = genCellGrid(width.value, length.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formater le timer en heure:minute:seconde
|
||||||
|
const formatTime = (seconds: number) => {
|
||||||
|
const hours = Math.floor(seconds / 3600)
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
|
const secs = seconds % 60
|
||||||
|
|
||||||
|
// Si les heures sont 0, ne pas les afficher
|
||||||
|
const hoursDisplay = hours > 0 ? `${hours}:` : ''
|
||||||
|
// Afficher les minutes avec un padding de 0 si elles sont inférieures à 10
|
||||||
|
const minutesDisplay = `${hours > 0 ? String(minutes).padStart(2, '0') : minutes}:`
|
||||||
|
// Toujours afficher les secondes avec un padding de 0
|
||||||
|
const secondsDisplay = String(secs).padStart(2, '0')
|
||||||
|
|
||||||
|
return `${hoursDisplay}${minutesDisplay}${secondsDisplay}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch pour démarrer ou arrêter le timer selon l'état du jeu
|
||||||
watch(gameStatus, async (status) => {
|
watch(gameStatus, async (status) => {
|
||||||
if (status === 1) {
|
if (status === 0) {
|
||||||
timerInterval = setInterval(() => timer.value++, 1000)
|
timer.value = 0
|
||||||
} else {
|
cellGrid.value = genCellGrid(width.value, length.value)
|
||||||
if (timerInterval !== null) clearInterval(timerInterval)
|
|
||||||
if (status === 2) alert('Vous avez gagné !')
|
|
||||||
if (status === 3) alert('Vous avez perdu !')
|
|
||||||
}
|
}
|
||||||
|
if (status === 1) timerInterval = setInterval(() => timer.value++, 1000)
|
||||||
|
else if (timerInterval) clearInterval(timerInterval)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Variables pour stocker la position du curseur
|
||||||
|
const mouseX = ref(0)
|
||||||
|
const mouseY = ref(0)
|
||||||
|
|
||||||
// État pour gérer le clic et la position de la cellule
|
// État pour gérer le clic et la position de la cellule
|
||||||
const isMouseDown = ref(false)
|
const isMouseDown = ref(false)
|
||||||
const currentCell = ref<{ rowIndex: number, cellIndex: number } | null>(null)
|
const currentCell = ref<{ rowIndex: number, cellIndex: number } | null>(null)
|
||||||
@@ -51,12 +78,12 @@ const handleMouseDown = (rowIndex: number, cellIndex: number) => {
|
|||||||
// Gestion des événements de relâchement de clic et de la position de la cellule
|
// Gestion des événements de relâchement de clic et de la position de la cellule
|
||||||
const handleMouseUp = async (rowIndex: number, cellIndex: number) => {
|
const handleMouseUp = async (rowIndex: number, cellIndex: number) => {
|
||||||
isMouseDown.value = false
|
isMouseDown.value = false
|
||||||
if (!mineGrid.length) {
|
if (!gameStatus.value) {
|
||||||
mineGrid = genMineGrid(width, length, nbMines, cellIndex, rowIndex)
|
mineGrid = genMineGrid(width.value, length.value, nbMines.value, cellIndex, rowIndex)
|
||||||
gameStatus.value = 1
|
gameStatus.value = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
cliqueGauche(gameStatus.value, cellGrid.value, mineGrid, cellIndex, rowIndex)
|
gameStatus.value = cliqueGauche(cellGrid.value, mineGrid, cellIndex, rowIndex)
|
||||||
currentCell.value = null
|
currentCell.value = null
|
||||||
|
|
||||||
// Réinitialiser les cases surlignées
|
// Réinitialiser les cases surlignées
|
||||||
@@ -91,6 +118,9 @@ const handleMouseMove = (rowIndex: number, cellIndex: number) => {
|
|||||||
|
|
||||||
// Gestion des événements de déplacement de la souris et de la position de la cellule
|
// Gestion des événements de déplacement de la souris et de la position de la cellule
|
||||||
const handleGlobalMouseMove = (event: MouseEvent) => {
|
const handleGlobalMouseMove = (event: MouseEvent) => {
|
||||||
|
mouseX.value = event.clientX
|
||||||
|
mouseY.value = event.clientY
|
||||||
|
|
||||||
if (isMouseDown.value && currentCell.value) {
|
if (isMouseDown.value && currentCell.value) {
|
||||||
const target = event.target as HTMLElement
|
const target = event.target as HTMLElement
|
||||||
|
|
||||||
@@ -125,49 +155,62 @@ onUnmounted(() => document.removeEventListener('mousemove', handleGlobalMouseMov
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<h1>Solo</h1>
|
<div class="space">
|
||||||
<div class="timer" :gameStatus>{{ timer }}</div>
|
<h1>Solo</h1>
|
||||||
<div class="grid">
|
<div class="timer" :gameStatus>{{ formatTime(timer) }}</div>
|
||||||
<div v-for="(row, rowIndex) in cellGrid" :key="rowIndex" class="row">
|
</div>
|
||||||
<div v-for="(cell, cellIndex) in row" :key="cellIndex">
|
|
||||||
<img class="cell" :src="cell"
|
<transition name="fade">
|
||||||
@mousedown.right="cliqueDroit(cellGrid, cellIndex, rowIndex)"
|
<div v-if="gameStatus === 2 || gameStatus === 3" class="message" :style="{ top: mouseY + 'px', left: mouseX + 'px' }">
|
||||||
@mousedown.left="handleMouseDown(rowIndex, cellIndex)"
|
{{ gameStatus === 2 ? 'Vous avez gagné !' : 'Vous avez perdu !' }}
|
||||||
@mouseup.left="handleMouseUp(rowIndex, cellIndex)"
|
</div>
|
||||||
@mousemove="handleMouseMove(rowIndex, cellIndex)"
|
</transition>
|
||||||
@contextmenu.prevent
|
|
||||||
@select.prevent
|
<div class="menu">
|
||||||
draggable="false"
|
<div class="presets">
|
||||||
/>
|
<button class="rounded" @click="width = 10; length = 15; nbMines = 20">Facile</button>
|
||||||
|
<button class="rounded" @click="width = 20; length = 50; nbMines = 180">Moyen</button>
|
||||||
|
<button class="rounded" @click="width = 50; length = 80; nbMines = 800">Difficile</button>
|
||||||
|
<button class="rounded" @click="width = 100; length = 100; nbMines = 2000">Extrême</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inputs">
|
||||||
|
<div>
|
||||||
|
<label for="length">Colonnes</label>
|
||||||
|
<input type="number" v-model="length" min="10" max="50" id="length" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="width">Lignes</label>
|
||||||
|
<input type="number" v-model="width" min="10" max="50" id="width" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="nbMines">Mines</label>
|
||||||
|
<input type="number" v-model="nbMines" min="10" max="250" id="nbMines" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button class="rounded newgame" @click="startNewGame">Nouvelle partie</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div v-for="(row, rowIndex) in cellGrid" :key="rowIndex" class="row">
|
<div v-for="(row, rowIndex) in cellGrid" :key="rowIndex" class="row">
|
||||||
<div v-for="(cell, cellIndex) in row" :key="cellIndex">
|
<img v-for="(cell, cellIndex) in row" :key="cellIndex" class="cell" :src="cell"
|
||||||
<img class="cell" :src="mineGrid.some(mine => mine[0] === cellIndex && mine[1] === rowIndex) ? '/sMine.png' : '/sEmpty.png'" />
|
@mousedown.right="gameStatus == 1 ? cliqueDroit(cellGrid, cellIndex, rowIndex) : null"
|
||||||
</div>
|
@mousedown.left="gameStatus == 0 || gameStatus == 1 ? handleMouseDown(rowIndex, cellIndex) : null"
|
||||||
|
@mouseup.left="gameStatus == 0 || gameStatus == 1 ? handleMouseUp(rowIndex, cellIndex) : null"
|
||||||
|
@mousemove="gameStatus == 0 || gameStatus == 1 ? handleMouseMove(rowIndex, cellIndex) : null"
|
||||||
|
@contextmenu.prevent
|
||||||
|
@select.prevent
|
||||||
|
draggable="false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.grid {
|
.main {
|
||||||
display: grid;
|
gap: 0
|
||||||
border: 10px solid black;
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
.cell {
|
|
||||||
flex: 1;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
text-align: center;
|
|
||||||
color: black;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
|
<p class="wip">
|
||||||
|
Le jeu est actuellement en cours de développement.
|
||||||
|
<br>
|
||||||
|
Le mode multijoueur n'est pas implémenté et l'affichage est encore en cours de finalisation.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h1>MultiMinesweeper</h1>
|
<h1>MultiMinesweeper</h1>
|
||||||
<p>
|
<p>
|
||||||
Bienvenue sur notre jeu de démineur en ligne !
|
Bienvenue sur notre jeu de démineur en ligne !
|
||||||
<br>
|
<br>
|
||||||
Pour jouer, il vous suffit de sélectionner le mode de jeu que vous souhaitez dans le menu ci-dessous.
|
Pour jouer, il vous suffit de sélectionner le mode de jeu que vous souhaitez dans le menu ci-dessous.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<router-link to="/game/solo" v-slot="{ navigate }">
|
<router-link to="/game/solo" v-slot="{ navigate }">
|
||||||
<button @click="navigate">Solo</button>
|
<button @click="navigate">Solo</button>
|
||||||
@@ -22,6 +29,16 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wip {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 60px;
|
||||||
|
right: 40px;
|
||||||
|
text-align: right;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
Reference in New Issue
Block a user