Electron Adventures: Episode 87: Svelte Drag and Drop Chess Board
Tags: javascript, electron, svelte
Published: 2021-11-06 14:55:41

Now that we can roll the dice on a screen (well, spin a wheel) without making the cat chase them like physical dice, we'd also like a game board.

The goal is not to implement full game with all the game logic, just enough interactions to let players play. And that basically means drag and drop for the game pieces.

Browsers supported drag and drog for very long time, but it's fairly boilerplate-heavy code. So before we write our own, let's see how Svelte ecosystem looks like, and give [svelte-dnd-action](https://github.com/isaacHagoel/svelte-dnd-action) a try.

To make things interesting let's make a chess board. It won't know any rules of chess, except for the initial starting position of pieces. You can drag them in any way you want.

### Grid structure

The layout of the app will be CSS grid. There will obviously be 8 columns. But there will be 10 rows. 8 regular rows, spacer row with nothing in it, and one big extra with big field to place killed pieces in.

### `initBoard`

Let's start with `initBoard` function, as it does a lot of things.

```javascript
  function initBoard() {
    let pieces = [
      "♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜",
      "♟︎", "♟︎", "♟︎", "♟︎", "♟︎", "♟︎", "♟︎", "♟︎",
      "", "", "", "", "", "", "", "",
      "", "", "", "", "", "", "", "",
      "", "", "", "", "", "", "", "",
      "", "", "", "", "", "", "", "",
      "♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙",
      "♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖",
      "",
    ]
    board = []

    let items, cls

    for (let i=0; i<65; i++) {
      if (pieces[i] === "") {
        items = []
      } else {
        items = [{id: i, symbol: pieces[i]}]
      }
      if (i === 64) {
        cls = "offboard"
      } else if ((i&1) ^ (Math.floor(i/8) & 1)) {
        cls = "light"
      } else {
        cls = "dark"
      }
      board.push({items, cls})
    }
  }
```

Each field is represented by an object with two fields - `items` (list of pieces it contains) and `cls` (CSS class).

`initBoard` needs to place the right chess pieces in the right places. To make drag and drop work, each piece must get a globally unique ID - we can just use `i` for that.

We also need to assign how each field looks like. Half will be one color, half will be the other color, and the final field will be `offboard` for pieces removed from the board.

There probably exists a simpler expression for choosing light/dark, this is a fun challenge if you'd like to play with that.

### `src/App.svelte`

```html
<script>
  import Field from "./Field.svelte"
  let board

  initBoard()
</script>

<div class="board">
  {#each board as field, i}
    <Field {...field} />
  {/each}
</div>

<style>
:global(body) {
  background-color: #aaa;
  color: #000;
  text-align: center;
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  user-select: none;
}

.board {
  display: grid;
  grid-template-columns: repeat(8, 100px);
  grid-template-rows: repeat(8, 100px) 50px 200px;
}
</style>
```

Now that we know how to initialize the board, `App` component is just some simple styling.

### `src/Field.svelte`

As logic is the same for regular fields and the offboard field for pieces removed from the board, I structured this component to support both roles, otherwise there would be a lot of duplication.

```html
<script>
  import Piece from "./Piece.svelte"
  import {dndzone} from "svelte-dnd-action"
  import {flip} from 'svelte/animate'

  export let cls
  export let items = []

  function handleDND(e) {
		items = e.detail.items
	}
</script>

<div class="field {cls}" use:dndzone={{items}} on:consider={handleDND} on:finalize={handleDND}>
  {#each items as item (item.id)}
    <div animate:flip>
      <Piece symbol={item.symbol} />
    </div>
  {/each}
</div>

<style>
.field {
  border: 2px solid green;
  margin: 0px;
  background-color: #aaf;
  display: flex;
  align-items: center;
  justify-content: center;
}
.dark {
  background-color: #afa;
}
.offboard {
  grid-column: 1 / span 8;
  grid-row: 10;
}
</style>
```

There are a few interesting things here.

`class="field {cls}"` lets `initBoard` function outside control class of each component.

There's extra `<div animate:flip>` that really looks like it should go inside `Piece` but unfortunately that's not how Svelte animations work - they need to be directly under keyed `#each` block in the same component. And we absolutely need those animations or drag and drop will have terrible jumps when pieces are moved around.

For drag and drop we need to pass a few things. `use:dndzone={{items}}` sets up drag and drop and tells it to store contents in `items`. We also set up `handleDND` as handler for both drop preview and final drop. As we don't have any fancy logic, that's enough.

### `src/Piece.svelte`

And finally `Piece` component, basically just some styling. It looks like it wants `<div animate:flip>`, but unfortunately that doesn't work, and we need to keep it outside.

```html
<script>
  export let symbol
</script>

<div>
  {symbol}
</div>

<style>
div {
  margin: 2px;
  height: 36px;
  width: 36px;
  font-size: 36px;
  line-height: 36px;
}
</style>
```

### Results

Here's the results, obviously ignoring the usual rules of chess:

![Episode 87 Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ygq6p9aeqkywiiy09g2.png)

The `svelte-dnd-action` library worked great, at least for this simple case.

With roulette wheel for dice, a board, and drag-and-droppable pieces, it's possible to make a lot of fun cat-proof board games. But let's set this aside for now, and for the next episode start another mini-project.

As usual, [all the code for the episode is here](https://github.com/taw/electron-adventures/tree/master/episode-87-svelte-drag-and-drop-chess-board).