Sprint 5 - Minesweeper
Learning Competencies
- Define and use as requested:
- Variables
- Objects
- Arrays
- Functions
- Loops
- Conditional Statements
- Event Listeners
- Create multiple small functions
- Use functions provided by others
- Debug code you have written.
Summary
Together we're going to be completing the classic game of Minesweeper. The game's objective is to clear a square grid containing hidden "mines" or bombs without detonating them. Clearing safe squares will give you clues about the number of neighbouring mines. To win the game, all mines must be "flagged", and all the safe squares revealed.
There are only two controls in Minesweeper:
Left click - reveal squaresRight click - flag squares
Before you jump into coding Minesweeper, you should play a couple of rounds to get the hang of it if you're unfamiliar with the game. Timebox it, though: don't get addicted!
Resources:
- Minesweeper - Google Game
- Minesweeper on CodePen by Andrey
Time Box
Challenge | Time |
---|---|
Minesweeper | 15 hours |
Reflect | 20 minutes |
And so it begins!
Let's get started by setting up our files.
Create a fork of the minesweeper repo
Clone your copy of the forked repository down using the SSH URL.
Navigate into the minesweeper directory and open it in your code editor. You'll see a stylesheet file, a JavaScript file, an HTML file, and some SVG images. There's also a
lib
directory.- You can ignore the images and CSS, although you can look at the styles if you're curious. You can also ignore the lib directory. It contains two JavaScript files, which do a lot of the heavy lifting for you. We do not expect you to understand what's in them (in fact, you should be able to complete the whole challenge without even glancing inside them).
Open the
index.html
in your code editor. You don't need to modify it, but it is helpful to have an idea of what's inside.- Notice that we have a containing
div
with the classboard
, but... it's empty! We're going to fill that up with cells, but rather than changing this file, we will do it using our JavaScript skills.
- Notice that we have a containing
Now open the
index.html
in your internet browser. You should see a message near the top of the screen telling you to "define a board object".
Creating the Board
Right then, let's get the first part working!
Open up minesweeper.js
and take a look. It contains hints for completing the various functions we will be writing, but to start:
Uncomment the
const board =
near the top and then complete the statement by adding{}
, defining the board as an empty object.Save the file and reload the page in your browser. It should tell you to add a cells property.
Add the property in your code, reload the page in the browser and see what the next message is. Keep repeating this cycle until you actually see a game board appear! Keep in mind:
You can make the board as large as 36 cells before the game complains it's too large. We suggest nine cells (three on each side) to begin with.
You should probably start all your mines as hidden: true!
Once you have a board displaying, you can play parts of the game!
Don't forget to commit your changes and push to GitHub!
Objects and Arrays
Ok, so we have a game board! Now we need to show how many mines are in surrounding squares as we reveal more of the board.
Previously we created our board
object. If we were describing our object in English, we'd say something like: "This is an object that contains an array of objects."
const board = {cells: [{row: 0,col: 0// ...},{row: 0,col: 1// ...}]}
Tip: if you're not sure if the board object you're creating is correct, open your browser's developer tools and type board. In Chrome, at least, this will show the object's structure, and you can expand the cells array to see its contents.
In Bootcamp, you'll need to access objects and arrays like this all the time! Getting familiar with them is crucial. Be sure that you understand the following before going onto the next part:
cells
is an object property.You can access (get) a property on an object by using either dot notation (
board.cells
) or bracket notation (board['cells']
).The value inside the brackets in bracket notation can be a string variable (
board[propertyName]
).You can access the first object inside the cells array using dot and array notation (
board.cells[0]
) or bracket and array notation (board['cells'][0]
).You can access a property on that object using dot and array notation (
board.cells[0].row
).
If you're not clear on any of these things, this is a perfect time to reach out and seek clarification! Ask your fellow Foundations students in Discord, ask in #code-help-desk, ask a Facilitator. Most importantly, practice! Sometimes the best way is to try it, either in the browser development tools or using an online tool like Repl.it.
Counting the Boom
So far, we've created a global
object, a data structure that we can use to track information about our game. It's worth noting that, as a rule, relying on objects in global scope isn't always such a great idea. However, it makes things a bit less complicated while learning the ropes! Once you've gotten the hang of working with objects and properties, you'll realise that we seldom need to keep them in global scope.
Now we need to find a way to display those counts of how many of the surrounding cells contain mines, the counts that make Minesweeper more than just a random guessing game. There's some slightly trickier code here, so we've provided some of it for you in lib.
Now, we're using our global board object to store information about the game board. One of the pieces of information we can store is how many mines surround each cell. We don't have to store where those mines are in relation to the cell, but we do have to count them. This will be used to display the hints that Minesweeper players get when they clear a cell, telling them where the next safe cell might be. Cell objects will end up looking something like this:
{row: 0,col: 1,isMine: false,isMarked: false,hidden: true,surroundingMines: 2}
In
startGame
, abovelib.initBoard()
, write afor
loop. This should loop through the contents ofboard.cells
. (Remember, board.cells is an array of objects.)The loop's only job should be to call
countSurroundingMines
once for each cell inboard.cells
. You'll need to pass each cell as an argument (the bit in the parentheses).Assign the result of
countSurroundingMines
to a property on each cell object. The new property should be calledsurroundingMines
.Further down in the file, you'll see the function
countSurroundingMines
. Your job is to define it, so it returns the number of cells around the current cell that have the isMine property set to true.Getting the cells that surround the current cell is tricky, so we've provided a helper function:2
lib.getSurroundingCells
. Read the comments abovecountSurroundingMines
for more clues.To be clear, you don't need to write
lib.getSurroundingCells
... you can just start using it!You'll need to use a syntax that looks something like:
const surroundingCells = getSurroundingCells(row, col);Think about how to get
row
andcol
out of your cell object: remember dot and bracket notation?
You're going to have to loop through the surrounding cells returned from
getSurroundingCells
, checking each one to see if it's a mine and adding to acount
variable if it is indeed a mine.Once you have the correct count, return it.
Once you've got the counts working to your satisfaction, commit your code!
Win some, lose some
So now we can play a game... almost! You've probably noticed that there's no real way to declare a winner. You can just continue merrily uncovering cells to your heart's content. We can change that now by creating a win condition: a way to tell our game that it's over.
It would be best if you went into this knowing that the way to win a Minesweeper game is to have correctly marked all of the mines and uncovered every other cell.
Remember event listeners? You used some of them in Sprint 3. In
startGame
, we will usedocument.addEventListener
to call checkForWin every time the left mouse button is clicked.When you've done that, add another one that calls
checkForWin
when the right mouse button (contextmenu
) is clicked. Remember, a player can win by clearing the last hidden cell or marking the last mine.Define the
checkForWin
function. It should loop through all ofboard.cells
.For each cell, check to see if both
.isMine
and.isMarked
are true. If any mines still exist that isn't marked, the player hasn't won yet, and you can return to exit out of the function.If every mine is marked, but there are still cells with the
hidden
property set to true, the player hasn't won yet, and you can return out of the function.If both these criteria pass, the player has won! There's a
displayMessage
function call at the bottom ofcheckForWin
you can use to tell them so.
Please take a moment to notice something: we're defining small functions that only have one job rather than big complex chunks of code. This is an essential principle of software development that we will touch on later: try to have your functions do one thing and name them appropriately.
When you're done, commit your code!
Hey, it's a game! Congratulations on making it this far! We hope you enjoyed (or at least weren't driven to distraction by) this little journey through a classic game. The next part contains some finishing off tasks and further goals. Even if you don't complete the goals, please do the When you're finished section below before moving on.
Finishing Touches
The basics are complete, but there are many more things to work on. Here are some more goals if you're done ahead of time and want to flex your skills. Please don't get too bogged down in trying to make your game perfect: it's much more important that you learn the principles behind it.
Goal 1 - automatically generate the board!
Instead of just typing out the global board
object, write a function to create it.
Each cell will need row
, col
, isMine
, isMarked
, and hidden
properties.
You could start by simply setting every isMine
to true, but later, you'll probably want to have a random number of mines scattered throughout the board.
Goal 2 - reset the board!
After a win or loss, give players a chance to try again by resetting the board to its default state. You'll need to put classes back the way they were at the start and re-initialize the global board object.
Goal 3 - sound effects!
Investigate using JavaScript to play a sound when the user uncovers or marks a cell. Play an explosion when they uncover a bomb and applause when they win.
Resource: Play sound on :hover
Goal 4 - you decide!
Let your imagination run rampant. Maybe they're not mines after all, but kumquats! Restyle the board, change the rules, make it your own.
When you've finished
So other people can play your game too, upload the game to GitHub Pages:
- Navigate to your minesweeper repo in your GitHub account
- Enter the settings menu for that repo and scroll down to the GitHub Pages section
- Under
Source
, select the main branch
This may take a short time to appear, but you will soon be able to go to USERNAME.github.io/minesweeper
and see your game online!
Share your link with your cohort and post the link to the #foundations channel on Discord with the hashtag #stretchMinesweeper to let other people play your game.
Reflection
Open my-reflections-sprint-5.md
in VS Code and add your reflections from this challenge under the Minesweeper
heading.
Commit and push to GitHub.