Lights Puzzle Outline
September 26, 2020
This is my first outline of a project I created a while ago. It's not going to delve deep into every piece of code, but an overview of what I was thinking when creating the project and some of the logistics I had to implement.
Inspiration
Inspired from Khanacademy's puzzles, I wanted to create my own project using the logic it would take to make this puzzle work.
Puzzles is something I'm really into and I try to incorporate more in my projects. This was my first complete Vue.js project.
Store
Vuex Opens in a new window is used as a global store to hold information for:- board status: The board is initialized based on what level is selected.
- moves count: Each time a grid is selected, the count increases until the game is reset or ends (then it's set to 0).
- level selected: Assigned before the game starts to set the correct board size.
- game progress: Set to
true
if the board is complete orfalse
if the user chooses to reset or end the game prematurely.
The 3 board levels are stored in a board.js
file to be used upon starting a new game.
export const boardLevel1 = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
];
export const boardLevel2 = [
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
];
export const boardLevel3 = [
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
];
Design & Colors
I used Vuetify Opens in a new window to display a "Google-like" design to the buttons and use a layout offered in the framework to structure the project.
Depending on the level, the board had a specific dimension to show the grids. I also had to implement slightly different sizes for the level 2 and 3 boards so they fit in mobile.
.row {
display: grid;
margin-bottom: 10px;
grid-gap: 5px;
&.rowLevel1 {
grid-template-rows: 100px;
grid-template-columns: repeat(3, 100px);
}
&.rowLevel2 {
grid-template-rows: 65px;
grid-template-columns: repeat(5, 65px);
}
&.rowLevel3 {
grid-template-rows: 45px;
grid-template-columns: repeat(7, 45px);
}
}
I set the color scheme in the base stylesheet base.scss
.
Components
Home
is where a level is selected to start the game.
The Board
passes each array of the board into the Row
. In each Row
, the elements in the arrays are assigned as Column
.
The Row
has minimal functionality whereas the Column
has a lot going on.
<!-- Column.vue -->
<template>
<div
class="column"
:ref="colRef"
:class="{ on: active, off: !active }"
@keydown.enter="changeStatus"
@click="changeStatus"
:tabindex="index_x === 0 && index_y === 0 ? 0 : -1"
@keydown.up="setFocus(index_x - 1, index_y)"
@keydown.down="setFocus(index_x + 1, index_y)"
@keydown.left="setFocus(index_x, index_y - 1)"
@keydown.right="setFocus(index_x, index_y + 1)"
@focus="setFocus(index_x, index_y)"
></div>
</template>
The class active
is set through a computed property determining if the grid's coordinates match the ones in focus in the store. This is called each time the moves count is increased (during every move).
active() {
if (this.moves) {
return this.$store.getters.isOn({
row: this.index_x,
col: this.index_y
});
}
}
To make this usable through the keyboard, I had to consider how the grid will be selected (enter key). That was the easy part, just call the changeStatus
function to a keydown.enter
event listener. The board's status is checked every time changeStatus
is called to end the game once it's solved.
Only the first top left grid is able to be tabbed to (so tabindex=0
and tabindex=-1
otherwise) so only arrow keys can roam the board.
Using the keydown
listener for all arrow key directions, they call the setFocus
method to the next grid the user is trying to select.
setFocus
sends the coordinates to the store so it's reached by every grid on the board to know which one should have the pink outline.
// Column.vue
setFocus (focusX, focusY) {
this.$store.dispatch('setFocus', {
x: focusX,
y: focusY
});
eventBus.$emit('changeFocus');
}
There is a unique ref
set to each grid (the first grid is col_0_0
). This is how to reach the grid that should be set to focus by checking if it matches the coordinates that are in the store.
// Column.vue
computed: {
focus() {
return this.$store.getters.getFocus;
},
colRef () {
return `col_${this.index_x}_${this.index_y}`;
}
},
mounted () {
// change focus depending on state's focus coordinates
eventBus.$on("changeFocus", event => {
const focusElem = `col_${this.focus.x}_${this.focus.y}`;
if (!!this.$refs[focusElem] && this.focus.x === this.index_x && this.focus.y === this.index_y) {
this.$refs[focusElem].focus();
}
});
}
Stats
sits next to the board and displays the current number of moves taken as well as the option to reset or end the game to select a new level.
Rules
uses a dropdown from Vuetify to show the description on how to solve the puzzle.
EndGame
shows if the game is won displaying how many moves it took to solve it along with the options to start the same game or select a new level.
Conclusion
This was my first complete puzzle I created when learning the Vue.js framework. I'm glad how it turned out and I hope this gave some insight to my process.
You can find the project on my GitHub Opens in a new window .