8 Queens Project Outline

Here with yet another puzzle project I created. The 8 Queens Puzzle is where you have to place 8 queen pieces on a chessboard where none of them can intercept the other 7. So you need to avoid sharing rows, columns, and diagonals with another queen.

I'm not sure how often this game is played in real life because how often do you have 8 separate queen chess pieces to do this with? Unless you collect chess boards...


Inspiration

I first heard of this game when I played Nancy Drew: Trail Of The Twister by Her Interactive a couple of years ago. There was this puzzle where you had to open 8 valves to turn the sprinkler system back on. My initial idea was to recreate the sprinkler puzzle until I knew it was taken from another puzzle so I changed my direction in making a chess themed layout.

Store

The store's state contains data for:

  • board
  • number of queens left to select
  • theme
  • number of invalid moves
  • info for what grid is focused on in terms of keyboard navigating
const state = {
    board: new Map(),
    availableQueens: 8,
    theme: 'classic',
    invalids: 0,
    focus: {
        x: 0,
        y: 0
    }
}

The board is a Map with each key's value holding a Set containing the grid coordinates it affects.

// a Queen's position contains all the squares it connects to
{
    "x,y" : [[x1,y1], [x2,y2]]
}

So if the square at [0,0] was selected, the Set would look like this:

{
    "0,0" : [[1,1], [2,2], [3,3], [4,4], [5,5], [6,6], [7,7]]
}

You'll notice this only lists the diagonal coordinates. That's because it's easy to know if another queen is in conflict with this one if either its x value or its y value are 0 too.

Helper

There is only one function I considered as a helper in the store. It returns the coordinates that are affected by the square selected.

// diagonals
const affectedAreas = (x, y) => {
    let areas = [];

    // [x-1,y-1]
    for(let i = x-1, j = y-1; i >= 0 && j >= 0; i--, j--) {
        areas.push([i,j]);
    }
    // [x-1,y+1]
    for(let i = x-1, j = y+1; i >= 0 && j < 8; i--, j++) {
        areas.push([i,j]);
    }
    // [x+1,y-1]
    for(let i = x+1, j = y-1; i < 8 && j >= 0; i++, j--) {
        areas.push([i,j]);
    }
    // [x+1,y+1]
    for(let i = x+1, j = y+1; i < 8 && j < 8; i++, j++) {
        areas.push([i,j]);
    }
    return areas;
}

export default affectedAreas;

Getters

There is one getter that helps to determine if the move is invalid (which causes the affected squares to outline in red).

invalidMove: (state) => (coords) => {
    let invalid = false;

    for (let [key, set] of state.board.entries()) {
        // check rows and columns
        let keyArr = key.split(",");
        //don't check its own coords (otherwise it will find itself and say it's invalid)
        if (!(parseInt(keyArr[0]) === coords.x && parseInt(keyArr[1]) === coords.y)) {

            //rows and columns (only matches the x OR the y, not both)
            if (parseInt(keyArr[0]) === coords.x || parseInt(keyArr[1]) === coords.y) {
                invalid = true;
            }
            //check diagonals
            for (let val of set.entries()) {
                if (val[0][0] === coords.x && val[0][1] === coords.y) {
                    invalid = true;
                }
            }
        }
    }
    return invalid;
}

This is used in the Square component to check that if it does have its own queen showing, then it needs to consider if it's now in conflict with another on the board. If it is, the invalids count in the store is increased.

Assets

One reason I wanted to make this project was to use my own created svgs in Inkscape. Using the wikipedia's image as inspiration, I created queen pieces for both the black and white and wood boards.

Dark Wood Queen Dark Wood Queen

Components

It's really only the Square component that has the most comprehensive logic.

<template>
  <div
    class="square"
    :ref="squareRef"
    :class="{
      'left-border': leftBorder,
      'right-border': rightBorder,
      'top-border': topBorder,
      'bottom-border': bottomBorder,
      'invalid': invalidMove,
      'wood-light': !dark && theme === 'wood',
      'wood-dark': dark && theme === 'wood',
      'classic-light':!dark && theme === 'classic',
      'classic-dark': dark && theme === 'classic'
    }"
    @keydown.enter="select"
    @click="select"
    :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)"
  >
    <img :src="queen" alt="Queen" class="queen" v-if="showQueen" />
  </div>
</template>

Look at all those styles and event handlers! Much of the bulk here is for making sure the border around the squares was uniform and to make sure keyboard control correctly outlines the square in focus with a blue border.

Conclusion

While I came across some hiccups along the way, I enjoyed making this project happen. I think one of the most difficult things for me was having a way to show if the user made an invalid move on the board which kept them from winning the game.

You can find the project on my GitHub .