Leave without saving?

Your editor changes will be lost.

Match-3-1 adds keyboard navigation, tile selection, and immediate swapping. The board remains a full 2D grid, but the player can now choose any two tiles and exchange their positions. There is deliberately no adjacency or match rule yet.

Match-3-0 explains the reusable Game architecture and checked-JavaScript setup. This lesson puts those injected services to work: PlayState receives input and sounds through its game service, rather than importing global objects.

Cursor Navigation

PlayState.enter() creates a cursor using the same position and dimensions as the board. Passing game gives the cursor access to input and sound services; passing the dimensions lets it stay inside this particular board.

this.cursor = new Cursor(
this.game,
this.board.x,
this.board.y,
this.board.width,
this.board.height
);

PlayState.update() updates the cursor every frame. Cursor.update() delegates to move(), which accepts WASD or arrow keys. When the position actually changes, it plays SoundName.Select1; setPosition() clamps the board coordinates between 0 and the board’s width or height minus one.

if (
input.isKeyPressed(Input.KEYS.W) ||
input.isKeyPressed(Input.KEYS.ARROW_UP)
) {
y--;
} else if (
input.isKeyPressed(Input.KEYS.S) ||
input.isKeyPressed(Input.KEYS.ARROW_DOWN)
) {
y++;
}
this.setPosition(x, y);

The board grid is indexed as tiles[row][column], so the cursor’s boardY is used first and its boardX second:

const highlightedTile = board.tiles[this.cursor.boardY][this.cursor.boardX];

Selecting Tiles

PlayState stores the first selected tile in selectedTile. Press Enter or Space on a tile to select it; press the same key on that tile again to clear the selection. Selecting a different tile calls swapTiles().

if (!this.selectedTile) {
this.selectedTile = highlightedTile;
return;
}
if (this.selectedTile === highlightedTile) {
this.selectedTile = undefined;
return;
}
this.swapTiles(board, highlightedTile);

At this stage, any two tiles may be swapped. Later lessons animate swaps, detect matches, and eventually restrict swaps to adjacent tiles.

Swapping the grid and the drawing position

A Tile has two related positions:

  • boardX and boardY identify its column and row in Board.tiles.
  • x and y identify where it is currently drawn within the board.

Board.swapTiles() must update both positions, then update both array slots. If only the array changes, the board’s data would be correct but the sprites would still appear in their old positions.

selectedTile.x = highlightedTile.x;
selectedTile.y = highlightedTile.y;
highlightedTile.x = temporaryTile.x;
highlightedTile.y = temporaryTile.y;
selectedTile.boardX = highlightedTile.boardX;
selectedTile.boardY = highlightedTile.boardY;
highlightedTile.boardX = temporaryTile.boardX;
highlightedTile.boardY = temporaryTile.boardY;
this.tiles[selectedTile.boardY][selectedTile.boardX] = selectedTile;
this.tiles[highlightedTile.boardY][highlightedTile.boardX] = highlightedTile;

The swap is immediate in Match-3-1. Match-3-2 replaces this direct visual position change with timer-driven tweens.

Player Feedback

PlayState.render() draws the board first, then the selected-tile overlay, cursor, and feedback text.

  • The selected tile receives a translucent white rounded rectangle.
  • The cursor draws a white rounded outline around its current grid cell.
  • The top-left display shows board coordinates, not canvas pixels:
Cursor: (x, y)
Selected: (x, y)

Before the player selects a tile, the second line is Selected: (-, -). This feedback makes the board coordinates and two-step selection flow visible while students test swapping.

Powered by WebContainers
Files
Preparing Environment