How to Represent a Shape With Points
Let's learn how shapes are rendered and represented in Phoenix LiveView.
Points representation
The user interface will render each pentomino with a set of points. Before we draw our points on the board, we need to understand where we will be placing those points. To be able to correctly calculate the location of each point in a shape, given that shape’s reflection and rotation, we’re going to take the following approach.
- Always plot each shape in the center of a 5x5 grid that will occupy the top-left of any given game board.
- Calculate the location of each point in the shape given its rotation and reflection within that 5x5 square.
- Only then will we apply the location to move the pentomino’s location on the wider board.
We’ll dig into this process and the reasoning behind it in greater detail later on. For now, we just need to understand that every shape is comprised of a set of five points, and those five points are located by default in the center of 5x5 square which is positioned like this:
Let’s do some quick prototyping. Consider the :p
shape. If we want to place the :p
shape in the center of the 5x5 square, it will look like this:
So, the :p
shape can be represented by a list of the following points:
[{3, 2}, {4,2}, {3, 3}, {4, 3}, {3, 4}]
Now that you’ve seen how a set of points is used to depict a shape on the board, let’s build out our very first core module, the Point
module.
Defininh the point constructor
We started by creating a file, penot/lib/pento/game/point.ex
, and implement the module head like this:
defmodule Pento.Game.Point do
Then, define the constructor function. The core entity that the Point
module creates and manages is the point tuple. The first element of the tuple is the x
coordinate of the point and the second value is the y
coordinate. So, our constructor function will take in two arguments, the x
and y
values of the point, and return the point tuple, as we can see here:
def new(x, y) when is_integer(x) and is_integer(y), do: {x, y}
Simple enough. The guards make sure each point has valid data, as long as we create points with the new
constructor. If bad data comes in, we just let it crash because we can’t do anything about that error condition. Now that we have a constructor to create points, let’s build some reducers to manipulate those points.
Moving points up and down with reducers
Later, we’ll build logic that moves the pentomino shape on the board by applying a change of location to each point in that shape. Right now, we’ll start from the ground up by giving our Point
module the ability to move an individual point by some amount.
In the Point
module at pento/lib/pento/game/point.ex
, we defined the move/2
function like this:
def move({x, y}, {change_x, change_y}) do{x + change_x, y + change_y}end
Here we have a classic reducer; its first argument of the data type we are manipulating, and the second argument is some input with which to manipulate it. Then, it returns a new entity that is the result of applying the manipulation to the data. In this case, we take in an amount by which to move each x
and y
coordinate and return a new ...