For me, the J programming language it's a very intriguing. It is full of ideas and concepts that I'm not familiar with. Getting to know a programming language it's not only about learning the syntax. It is learning the techniques that people use to take advantage of it what gives you more insight . This is particularly true for J.
For me the best way to learn more about a programming language is to try to solve a small problem with it. In this post I'm going to describe an attempt to write a small and incomplete Tetris-like clone using J and the ncurses library. Here's a preview of how it looks:
Tetriminos
According to the Wikipedia page for Tetris, the pieces are named Tetriminos https://en.wikipedia.org/wiki/Tetris#Gameplay. Each piece is composed of blocks. In J we can represent this pieces as matrices.
To create this matrices we use the Shape verb ($) For example:
- The "L" tetrimino:
] l_tetrimino =. 2 3 $ 1 1 1 1 0 0
1 1 1
1 0 0
- The "O" tetrimino:
] b_tetrimino =. 2 2 $ 4 4 4 4
4 4
4 4
- The "S" tetrimino:
] s_tetrimino =. 2 3 $ 0 5 5 5 5 0
0 5 5
5 5 0
Tetrimino rotation
In Tetris pressing the 'up' arrow is going to rotate the current piece. We can use matrix Transpose (|:) and Reverse (|.) verbs and compose them together using the Atop conjunction (@). Here's the definition:
rotate =: |.@|:
Here we can see how this verb works:
l_tetrimino
1 1 1
1 0 0
rotate l_tetrimino
1 0
1 0
1 1
rotate rotate l_tetrimino
0 0 1
1 1 1
rotate rotate rotate l_tetrimino
1 1
0 1
0 1
rotate rotate rotate rotate l_tetrimino
1 1 1
1 0 0
We can apply this transformation to the other tetriminos for example:
] s_tetrimino =. 2 3 $ 0 5 5 5 5 0
0 5 5
5 5 0
rotate s_tetrimino
5 0
5 5
0 5
rotate rotate s_tetrimino
0 5 5
5 5 0
We use different numbers for each tetrimino so we can use different colors to paint them.
Tetrimino placement
A natural way to model the game is to use a matrix representing the playing field. We use a 10 columns by 20 rows matrix for this effect. We use the Shape verb ($) to do this:
] game =: 20 10 $ 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 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 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 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 0 0 0 0 0
A fundamental piece of functionality that we need is a way to put a tetrimino inside this matrix. This proved to be tricky (maybe because of lack of J knowledge). We're going to incrementally create this verb.
Reading the J documentation, it seems that we can use the Amend (m } _ _ _) verb to change just a set of cells of the game matrix. Here's an example on how to use this verb
] sample =. 5 5 $ 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
1 2 3 4 (1 1;2 1;1 2;2 2) } sample
0 0 0 0 0
0 1 3 0 0
0 2 4 0 0
0 0 0 0 0
0 0 0 0 0
What we are saying here is that we can to change the following cells in sample
:
- row 1 column 1 with value 1
- row 2 column 1 with value 2
- row 1 column 2 with value 3
- row 2 column 2 with value 4
Now to take advantage of this verb we need to calculate the target coordinates to change the value of a tetrimino. First we start by generating coordinates for each of the cells of the tetrimino.
We're going to use the following predifined values:
] l_tetrimino =. 2 3 $ 1 1 1 1 0 0
1 1 1
1 0 0
sample
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
We start by determining how many rows and columns. We use the Shape of verb ($) to do this:
$ l_tetrimino
2 3
Now we want to generate (0, 0);(0, 1);...(1, 2);(2, 2). With the result of Shape of we generate a sequence of numbers for each of the axis. To do this we use the Integers (i.) verbwith the number of rows and the number of columns. For example:
NB. Get the number of rows:
(0&{@$) l_tetrimino
2
NB. Get the number of columns:
(1&{@$) l_tetrimino
3
NB. Get an integer sequence from zero to number of rows or columns
i.(1&{@$) l_tetrimino
0 1 2
i.(0&{@$) l_tetrimino
0 1
Now this is very cool, we can use the Table verb (/)to pair this two arrays. From the documentation:
In general, each cell of x is applied to the entire of y . Thus x u/ y is equivalent to x u"(lu,_) y where lu is the left rank of u .
This is very important!. To taken advantage of this we need to use the Append verb (,)
0 (,"0) 0 1 2
0 0
0 1
0 2
Now we can take advantage of this and write:
( (i.@(0&{@$)) (<@,"0)/ (i.@(1&{@$))) l_tetrimino
+---+---+---+
|0 0|0 1|0 2|
+---+---+---+
|1 0|1 1|1 2|
+---+---+---+
Now this is almost what we need. We can use the Ravel veb (,) to flatten this box:
, ( (i.@(0&{@$)) (<@,"0)/ (i.@(1&{@$))) l_tetrimino
+---+---+---+---+---+---+
|0 0|0 1|0 2|1 0|1 1|1 2|
+---+---+---+---+---+---+
With this positions we can use the Amend verb to change our game matrix:
(,l_tetrimino) positions } sample
1 1 1 0 0
1 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
We need something else since this technique only allows us to put the tetrimino at the top of the matrix. In order to do this we need to sum the target position to the coordinates that we generated. We can use the powerful Under verb (&.) which allows us to apply an operation to each of the cells of a box.
(3 2)&+ &.> positions
+---+---+---+---+---+---+
|3 2|3 3|3 4|4 2|4 3|4 4|
+---+---+---+---+---+---+
We construct this operation by:
- using Bond conjuction (&) to tie together the position (3 2) with the plus operation (+) . That is
(3 2)&+
. - we apply this operation to each of the elements of the box and then assemble the box again. That is
&.>
Now we can put the tetrimino in row 3, column 2.
target_position =. 3 2
target_position =. 2 1
(,l_tetrimino) (target_position&+&.>positions) } sample
0 0 0 0 0
0 0 0 0 0
0 1 0 0 0
0 1 1 1 0
0 0 0 0 0
We cannot just pust the tetrimino in the target position. It may also "blend" with existing values. For example say the following game field and the following tetrminio:
] game
0 0 0 0 0
0 0 0 0 0
0 0 0 1 0
0 0 0 1 0
0 0 0 1 1
positions =., ( (i.@(0&{@$)) (<@,"0)/ (i.@(1&{@$))) tetrimino
(,tetrimino) ((1 2)&+&.> positions) } game
0 0 0 0 0
0 0 1 1 0
0 0 1 0 0
0 0 1 0 0
0 0 0 1 1
Because of this we need to mix the tetrimino with the current values of the target region. We do this by extracting the values of the target position:
($tetrimino) $ ((1 2)&+&.> positions) { game
0 0
0 1
0 1
We can combine this array with the tetrimino and we get the desired target value:
] target_tetrimino =. +&tetrimino ($tetrimino) $ ((1 2)&+&.> positions) { game
1 1
1 1
1 1
(,target_tetrimino) ((1 2)&+&.> positions) } game
0 0 0 0 0
0 0 1 1 0
0 0 1 1 0
0 0 1 1 0
0 0 0 1 1
The final verb definition looks like this:
put_in_matrix =: 3 : 0
NB. unpack argumets
tetrimino =. > 0 { y
i =. > 1 { y
j =. > 2 { y
game =. > 3 { y
NB. calculate target positions
positions =. , ((i.@(0&{)@$)(<@,"0)/(i.@(1&{)@$)) tetrimino
NB. combine tetrimino with target section
tetrimino =. +&tetrimino ($tetrimino) $ ((+&(i,j))&.> positions) { game
NB. change matrix
(,tetrimino) ((+&(i,j))&.> positions)} game
)
Checking if space is available
The other piece of functionality that we need is a way to verify if we can put the tetrimino in a target position. We need to verify two conditions: 1. We can put the tetrimino inside the game field. 2. There's space available in the target position.
To check the boundaries we use common comparison operators:
NB. Verify field boundaries
is_inside =. (xpos >: 0) *. (ypos >: 0) *. ( (xpos+tetrimino_width - 1) < game_width) *. ((ypos+tetrimino_height - 1) < game_height)
The second criteria it's more intersesting. To illustrate how we did the detection we're going to start with a predifined game field:
game
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 1 0
0 0 0 0 0
] tetrimino =. 2 3 $ 2 0 0 2 2 2
2 0 0
2 2 2
The first step is to reset the values of the tetrimino to be either zero or one:
] tetrimino =. 0&< tetrimino
1 0 0
1 1 1
Now we extract the elements of the target position (in this example column 1, row 3)
ypos =. 3
xpos =. 1
positions =. , ((i.@(0&{)@$)(<@,"0)/(i.@(1&{)@$)) tetrimino
($tetrimino) $ ((+&(ypos,xpos))&.> positions){ game
0 0 1
0 0 0
xpos =. 1
ypos =. 2
positions =. , ((i.@(0&{)@$)(<@,"0)/(i.@(1&{)@$)) tetrimino
($tetrimino) $ ((+&(ypos,xpos))&.> positions){ game
0 0 0
0 0 1
Now we can multiply the tetrimino by the target value:
target_segment =. ($tetrimino) $ ((+&(ypos,xpos))&.> positions){ game
] hits =. +/ , *&tetrimino target_segment
1
Now the variable hits
contains the number of elements with a target cell value. The final predicate looks like this:
can_put_in_matrix =: 3 : 0
NB. Unpack the arguments
tetrimino =. 0&< > 0 { y
tetrimino_width =. 1 { $ tetrimino
tetrimino_height =. 0 { $ tetrimino
ypos =. > 1 { y
xpos =. > 2 { y
game =. > 3 { y
game_width =. 1 { $ game
game_height =. 0 { $ game
NB. Verify field boundaries
is_inside =. (xpos >: 0) *. (ypos >: 0) *. ( (xpos+tetrimino_width - 1) < game_width) *. ((ypos+tetrimino_height - 1) < game_height)
NB. Check if we hit an occupied cell
hits =. 0
if. is_inside do.
positions =. , ((i.@(0&{)@$)(<@,"0)/(i.@(1&{)@$)) tetrimino
hits =. +/ , *&tetrimino ($tetrimino) $ ((+&(ypos,xpos))&.> positions){ game
end.
is_inside *. (hits = 0)
)
End words
As it was said in the beginning, J it's very interesting. For me there are many things to learn (you can tell that by looking at all those parenthesis in some expressions). Also there are many strategies in array languages that one needs to understand in other to write idiomatic code.
The ncurses interface will be discussed in part 2. For future posts it will be interesting to talk about concepts like the obverse
(http://www.jsoftware.com/help/jforc/u_1_uv_uv_and_u_v.htm#_Toc191734413) and state machines
(http://www.jsoftware.com/help/jforc/loopless_code_vii_sequential.htm#_Toc191734470) .
Code for this post can be found here: https://github.com/ldfallas/jcurtris