|
| 1 | +use "std" |
| 2 | +use "math" |
| 3 | +use "types" |
| 4 | +use "canvasfx" |
| 5 | + |
| 6 | +// Constants |
| 7 | +CELL_NONE = -100 |
| 8 | +CELL_MINE = -200 |
| 9 | +// Colors |
| 10 | +BACKGROUND_COLOR = Color.new(#FF283593) |
| 11 | +OPENED_CELL_COLOR = Color.new(0xFF9FA8DA) |
| 12 | +DEFAULT_CELL_COLOR = Color.new(#FF5C6BC0) |
| 13 | +MINE_CELL_COLOR = Color.new(#FF1A237E) |
| 14 | +FLAG_COLOR = Color.new(#FF7A231E) |
| 15 | + |
| 16 | +// Parameters |
| 17 | +WIDTH = 400 HEIGHT = 400 |
| 18 | +TABLE_WIDTH = 6 |
| 19 | +TABLE_HEIGHT = 6 |
| 20 | + |
| 21 | +// Other |
| 22 | +isGameFinished = false |
| 23 | +gridStepX = WIDTH / double(TABLE_WIDTH) |
| 24 | +gridStepY = HEIGHT / double(TABLE_HEIGHT) |
| 25 | + |
| 26 | +// Graphics and event listeners initialization |
| 27 | +g = window("MineSweeper", WIDTH, HEIGHT) |
| 28 | +addEventHandler(Events.MOUSE_CLICKED, ::onMouseClicked) |
| 29 | + |
| 30 | +// Create table with mines |
| 31 | +TABLE = [] |
| 32 | +FLAGS = [] |
| 33 | +newGame() |
| 34 | +def newGame() { |
| 35 | + isGameFinished = false |
| 36 | + TABLE = newarray(TABLE_HEIGHT, TABLE_WIDTH) |
| 37 | + FLAGS = newarray(TABLE_HEIGHT, TABLE_WIDTH) |
| 38 | + for i = 0, i < TABLE_WIDTH, i++ |
| 39 | + for j = 0, j < TABLE_HEIGHT, j++ |
| 40 | + TABLE[j][i] = CELL_NONE |
| 41 | + maxMines = int(sqrt(rand(1, 4) * TABLE_WIDTH * TABLE_HEIGHT)) |
| 42 | + for i = 0, i < maxMines, i++ |
| 43 | + TABLE[rand(TABLE_HEIGHT)][rand(TABLE_WIDTH)] = CELL_MINE |
| 44 | + |
| 45 | + g.setStroke(Color.DARKSLATEGREY) |
| 46 | + g.setLineWidth(5) |
| 47 | + g.setTextAlign(TextAlignment.CENTER) |
| 48 | + g.setFill(BACKGROUND_COLOR) |
| 49 | + g.fillRect(0, 0, WIDTH, HEIGHT) |
| 50 | + drawGameTable() |
| 51 | +} |
| 52 | + |
| 53 | +def drawGameTable(showBombs = false) { |
| 54 | + for i = 0, i < TABLE_WIDTH, i++ { |
| 55 | + for j = 0, j < TABLE_HEIGHT, j++ { |
| 56 | + match TABLE[j][i] { |
| 57 | + case CELL_NONE: g.setFill(DEFAULT_CELL_COLOR) |
| 58 | + case CELL_MINE if showBombs: g.setFill(MINE_CELL_COLOR) |
| 59 | + case CELL_MINE if !showBombs: g.setFill(DEFAULT_CELL_COLOR) |
| 60 | + case _ : g.setFill(OPENED_CELL_COLOR) |
| 61 | + } |
| 62 | + if (FLAGS[j][i] && (TABLE[j][i] == CELL_NONE || TABLE[j][i] == CELL_MINE) { |
| 63 | + g.setFill(FLAG_COLOR) |
| 64 | + } |
| 65 | + g.fillRect(i * gridStepX + 1, j * gridStepY + 1, gridStepX - 2, gridStepY - 2) |
| 66 | + if (TABLE[j][i] >= 0) { |
| 67 | + g.setFill(Color.BLACK) |
| 68 | + g.fillText(TABLE[j][i], i * gridStepX + gridStepX / 2, j * gridStepY + gridStepY / 2) |
| 69 | + } |
| 70 | + } |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +def drawWin() { |
| 75 | + drawGameTable(true) |
| 76 | + g.setFill(Color.new(#60FFFFFF)) |
| 77 | + g.fillRect(0, 0, WIDTH, HEIGHT) |
| 78 | + g.setFill(Color.DARKGREEN) |
| 79 | + g.fillText("YOU WIN", WIDTH / 2, HEIGHT / 2) |
| 80 | +} |
| 81 | + |
| 82 | +def drawGameOver() { |
| 83 | + drawGameTable(true) |
| 84 | + g.setFill(Color.new(#60000000)) |
| 85 | + g.fillRect(0, 0, WIDTH, HEIGHT) |
| 86 | + g.setFill(Color.PINK) |
| 87 | + g.fillText("Game Over", WIDTH / 2, HEIGHT / 2) |
| 88 | +} |
| 89 | + |
| 90 | + |
| 91 | +def onMouseClicked(e) { |
| 92 | + if (isGameFinished) { |
| 93 | + newGame() |
| 94 | + return 0 |
| 95 | + } |
| 96 | + tableX = int(e.x / gridStepX) |
| 97 | + tableY = int(e.y / gridStepY) |
| 98 | + if (tableX < 0 || tableY < 0 || |
| 99 | + tableX >= TABLE_WIDTH || tableY >= TABLE_HEIGHT) return 0 |
| 100 | + |
| 101 | + if (e.button == MouseButton.SECONDARY) { |
| 102 | + FLAGS[tableY][tableX] = 1 - FLAGS[tableY][tableX] |
| 103 | + drawGameTable() |
| 104 | + return 0 |
| 105 | + } |
| 106 | + if (TABLE[tableY][tableX] == CELL_MINE) { |
| 107 | + isGameFinished = true |
| 108 | + drawGameOver() |
| 109 | + return 0 |
| 110 | + } |
| 111 | + updateCell(tableX, tableY) |
| 112 | + if (gameFinished()) { |
| 113 | + isGameFinished = true |
| 114 | + drawWin() |
| 115 | + return 0 |
| 116 | + } |
| 117 | + drawGameTable() |
| 118 | +} |
| 119 | + |
| 120 | +def updateCell(tx, ty, visited = []) { |
| 121 | + if (tx < 0 || ty < 0 || |
| 122 | + tx >= TABLE_WIDTH || ty >= TABLE_HEIGHT) return visited |
| 123 | + for v : visited { |
| 124 | + if [tx, ty] == v return visited |
| 125 | + } |
| 126 | + minesCount = calculateMinesCount(tx, ty) |
| 127 | + TABLE[ty][tx] = minesCount |
| 128 | + if (minesCount != 0) return visited |
| 129 | + visited ::= [tx, ty] |
| 130 | + if (tx >= 1 && ty >= 1) visited = updateCell(tx - 1, ty - 1, visited) |
| 131 | + if (ty >= 1) visited = updateCell(tx, ty - 1, visited) |
| 132 | + if (tx < WIDTH - 1 && ty >= 1) visited = updateCell(tx + 1, ty - 1, visited) |
| 133 | + |
| 134 | + if (tx >= 1) visited = updateCell(tx - 1, ty, visited) |
| 135 | + if (tx < WIDTH - 1) visited = updateCell(tx + 1, ty, visited) |
| 136 | + |
| 137 | + if (tx >= 1 && ty < HEIGHT - 1) visited = updateCell(tx - 1, ty + 1, visited) |
| 138 | + if (ty < HEIGHT - 1) visited = updateCell(tx, ty + 1, visited) |
| 139 | + if (tx < WIDTH - 1 && ty < HEIGHT - 1) visited = updateCell(tx + 1, ty + 1, visited) |
| 140 | + return visited |
| 141 | +} |
| 142 | + |
| 143 | +def calculateMinesCount(x, y) { |
| 144 | + count = 0 |
| 145 | + for dx = -1, dx <= 1, dx++ { |
| 146 | + for dy = -1, dy <= 1, dy++ { |
| 147 | + // Skip center [x, y] cell |
| 148 | + if ( (dx == 0) && (dy == 0) ) continue |
| 149 | + |
| 150 | + xx = x + dx |
| 151 | + yy = y + dy |
| 152 | + if (xx < 0 || yy < 0 || |
| 153 | + xx >= TABLE_WIDTH || yy >= TABLE_HEIGHT) continue |
| 154 | + count += (TABLE[yy][xx] == CELL_MINE ? 1 : 0) |
| 155 | + } |
| 156 | + } |
| 157 | + return count |
| 158 | +} |
| 159 | + |
| 160 | +def gameFinished() { |
| 161 | + for i = 0, i < TABLE_WIDTH, i++ { |
| 162 | + for j = 0, j < TABLE_HEIGHT, j++ { |
| 163 | + if (TABLE[j][i] == CELL_NONE) return false |
| 164 | + } |
| 165 | + } |
| 166 | + return true |
| 167 | +} |
0 commit comments