Newbie Phaser 3-rotate match3 game board after each score

Hi,

I am new to using Phaser 3, trying to learn using a nice game animation sample done by Feronato for match 3. (Source files at end of article.)

The challenge I need some suggestions for is how to possibly use Phaser 3 to add rotation to the game board after each match (eventually will also try to add scoring after each match). Random rotation clockwise or counterclockwise for 90, 180, 270 or 360 degrees.

I suspect as a newbie, reading help materials, checking a few examples, that I would have to group all of the tiles in the board, rotate all, or a more economic solution, if possible, would be to rotate the entire game board with all child pieces (tiles) at once.

The trick is after the rotation not to loose the ability to click/select a tile and instead offset it onto another tile somewhere else on the game board. So, would appreciate any suggestions/ leads, examples to get me started with setting up this game board rotation while retaining the correct click-ability of each tile after rotation. Thank you in advance for your help. (If done without Phaser, using canvas transforms, calculating the offsets, saving/restoring/redrawing the context is super complex. Hoping Phaser can make this easier.)

You have to rotate gameArray.

const reverse = array => [...array].reverse();
const compose = (a, b) => x => a(b(x));

const flipMatrix = matrix => (
  matrix[0].map((column, index) => (
    matrix.map(row => row[index])
  ))
);

const rotateMatrix = compose(flipMatrix, reverse);
const flipMatrixCounterClockwise = compose(reverse, rotateMatrix);
const rotateMatrixCounterClockwise = compose(reverse, flipMatrix);

You could store the old gameArray, and tween each sprite from old to new. If you actually rotate the graphics (just draw on a RenderTexture), the orientation would be messed up. But it would be more clear what’s happening, and you could just redraw after the rotation is finished.

Hi Milton,
Thank you for these ideas on how to do this. New to Phaser, it will take some additional learning on my part to see if I can implement this.

Interestingly, Feronato did create a Phaser 2 version with rotation (the one I put a link to is Phaser 3 version). However, this would require me to figure out how to rewrite the Phaser 2 rotation code in the Phaser 2 example and add it into the Phaser 3 example (which currently does not have rotation). Whew! (link to Phaser 2 example).

Ah, that’s even easier.
Just use Phaser.Utils.Array.Matrix.RotateMatrix().

And the pivot group code should work pretty much as is (and indeed the orientation remains changed, but it looks intentional, as the sprites look good whichever way they are rotated).

Thank you, Milton.
I will play with the code, see if I can figure out how to transpose the rotation in Feronato’s Phaser 2 script to his Phaser 3 script, in the 2 examples. Looks like the syntax between Phaser 2 and Phaser 3 are different, so I’ll have to try to figure out how to “translate” from one to the other to get the same effect, not throw any coding errors.
From (Phaser 2):
function replenishField(){
var replenished = 0;
var restart = false;
for(var j = 0; j < fieldSize; j++){
var emptySpots = holesInCol(j);
if(emptySpots > 0){
if(!fastFall && emptySpots > 1){
emptySpots = 1;
restart = true;
}
for(i = 0; i < emptySpots; i++){
var orb = game.add.sprite(orbSize * j + orbSize / 2, - (orbSize * (emptySpots - 1 - i) + orbSize / 2), “orbs”);
orb.anchor.set(0.5);
orbGroup.add(orb);
var randomColor = game.rnd.between(0, orbColors - 1);
orb.frame = randomColor;
gameArray[i][j] = {
orbColor: randomColor,
orbSprite: orb
}
var orb2Tween = game.add.tween(gameArray[i][j].orbSprite).to({
y: orbSize * i + orbSize / 2
}, fallSpeed, Phaser.Easing.Linear.None, true);
replenished ++;
orb2Tween.onComplete.add(function(){
replenished --;
if(replenished == 0){
if(restart){
makeOrbsFall();
}
else{
if(matchInBoard()){
game.time.events.add(250, handleMatches);
}
else{
var rotateBoard = game.add.tween(orbGroup).to({
angle: 90
}, 1000, Phaser.Easing.Bounce.Out, true);
rotateBoard.onComplete.add(function(){
gameArray = Phaser.ArrayUtils.rotateMatrix(gameArray, -90);
for(var i = 0; i < fieldSize; i++){
for(var j = 0; j < fieldSize; j++){
gameArray[i][j].orbSprite.angle += 90;
gameArray[i][j].orbSprite.x = j * orbSize + orbSize / 2;
gameArray[i][j].orbSprite.y = i * orbSize + orbSize / 2;
}
}
orbGroup.angle = 0;
canPick = true;
selectedOrb = null;
showSuggestion();
}, this)
}
}
}
})
}
}
}
}

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
To (Phaser 3):
replenishField(){
let replenished = 0;
for(let j = 0; j < gameOptions.fieldSize; j ++){
let emptySpots = this.holesInCol(j);
if(emptySpots > 0){
for(let i = 0; i < emptySpots; i ++){
replenished ++;
let randomColor = Phaser.Math.Between(0, gameOptions.gemColors - 1);
this.gameArray[i][j].gemColor = randomColor;
this.gameArray[i][j].gemSprite = this.poolArray.pop()
this.gameArray[i][j].gemSprite.setFrame(randomColor);
this.gameArray[i][j].gemSprite.visible = true;
this.gameArray[i][j].gemSprite.x = gameOptions.gemSize * j + gameOptions.gemSize / 2;
this.gameArray[i][j].gemSprite.y = gameOptions.gemSize / 2 - (emptySpots - i) * gameOptions.gemSize;
this.gameArray[i][j].gemSprite.alpha = 1;
this.gameArray[i][j].isEmpty = false;
this.tweens.add({
targets: this.gameArray[i][j].gemSprite,
y: gameOptions.gemSize * i + gameOptions.gemSize / 2,
duration: gameOptions.fallSpeed * emptySpots,
callbackScope: this,
onComplete: function(){
replenished --;
if(replenished == 0){
if(this.matchInBoard()){
this.time.addEvent({
delay: 250,
callback: this.handleMatches()
});
}
else{
this.canPick = true;
this.selectedGem = null;
}
}
}
});
}
}
}
}

Update: so far, no joy translating the rotate function from Phaser 2 script to Phaser 3. Even after several fixes (like “let” for “var”), just getting black screen when I view the final HTML, telling me the code is not yet correct. I suspect there is a need for much more re-writing (variables, etc.)? For example, in Phaser 3 script, “this.canPick = true;” is different from the Phaser 2 script representation: “canPick = true;”. So, need to re-write, if I can figure which and how. Ugh!

this.gameArray = Phaser.Utils.Array.Matrix.RotateMatrix(this.gameArray, -90);
for (var i = 0; i < gameOptions.fieldSize; i++) {
    for (var j = 0; j < gameOptions.fieldSize; j++) {
        this.gameArray[i][j].gemSprite.x = gameOptions.gemSize * j + gameOptions.gemSize / 2;
        this.gameArray[i][j].gemSprite.y = gameOptions.gemSize * i + gameOptions.gemSize / 2;
    }   
}         

This is at least functional, now you just need to use a group rotate. Not sure how Phaser 3 does that, I’ll have a look later…

Thank you again, Milton. So far, I am stuck, so if you can take a look later, suggest further how to fix, this would be a big help. Not sure how to use the above script, where it would go, how to call it-create new function, add listener, run it in a button to test?

Just put it above this.canPick = true; in replenishField().

OK, if I simply pop it in, it seems to work, but the rotation is instantaneous, so the only way I knew it had rotated was to note a specific corner tile, see if it moved.
Is there any way to slow the rotation/animation, have the rotation occur over 2 or 3 seconds? (setTimeout didn’t work)?
I have been looking at the RotateMatrix Phaser 3 API and so far haven’t found a time property?

Those are 2 separate things. You have to ‘rotate’ the underlying array, that’s the Matrix util class. This is instantaneous, you can’t tween that.
But you can tween graphics. Something like this:

this.tweens.addCounter({
    from: 0,
    to: 360,
    duration: 3000,
    repeat: -1,
    onUpdate: function(tween) {
        this.gemGroup.setAngle(tween.getValue());
    },
    delay: 1000
});                

And onComplete call the Matrix rotation.

Hi @saratogacoach ,
One possible solution would be make an scene for the board and rotate the camera:

scene.cameras.main.setAngle(90) // Rotate de camera 90 degrees

Or you can apply a tween on the angle property of the camera.

Thank you Milton and JJ for these helpful additional suggestions. A Tween that can set timing duration would be helpful, so I can see if I can add it. Again, your help is much appreciated.

I had to use a container. Group doesn’t work anymore for display purposes. Since you can’t set origin for a container, all kinds of places need an offset…
Here’s the diff from the original game.js. You can test it here.

45c45
<         this.gemGroup = this.add.group();
---
>         this.gemGroup = this.add.container(350, 350);
49c49
<                 let gem = this.add.sprite(gameOptions.gemSize * j + gameOptions.gemSize / 2, gameOptions.gemSize * i + gameOptions.gemSize / 2, "gems");
---
>                 let gem = this.add.sprite(gameOptions.gemSize * j + (gameOptions.gemSize / 2) - 350, gameOptions.gemSize * i + (gameOptions.gemSize / 2) - 350, "gems");
142c142
<         return Math.floor(gem.gemSprite.y / gameOptions.gemSize);
---
>         return Math.floor((gem.gemSprite.y + 350) / gameOptions.gemSize);
145c145
<         return Math.floor(gem.gemSprite.x / gameOptions.gemSize);
---
>         return Math.floor((gem.gemSprite.x + 350) / gameOptions.gemSize);
173,174c173,174
<             x: col * gameOptions.gemSize + gameOptions.gemSize / 2,
<             y: row * gameOptions.gemSize + gameOptions.gemSize / 2,
---
>             x: col * gameOptions.gemSize + gameOptions.gemSize / 2 - 350,
>             y: row * gameOptions.gemSize + gameOptions.gemSize / 2 - 350,
322,323c322,323
<                     this.gameArray[i][j].gemSprite.x = gameOptions.gemSize * j + gameOptions.gemSize / 2;
<                     this.gameArray[i][j].gemSprite.y = gameOptions.gemSize / 2 - (emptySpots - i) * gameOptions.gemSize;
---
>                     this.gameArray[i][j].gemSprite.x = gameOptions.gemSize * j + gameOptions.gemSize / 2 - 350;
>                     this.gameArray[i][j].gemSprite.y = gameOptions.gemSize / 2 - (emptySpots - i) * gameOptions.gemSize - 350;
328c328
<                         y: gameOptions.gemSize * i + gameOptions.gemSize / 2,
---
>                         y: gameOptions.gemSize * i + gameOptions.gemSize / 2 - 350,
340,341c340,362
<                                     this.canPick = true;
<                                     this.selectedGem = null;
---
>                                     var _this = this;
>                                     this.tweens.addCounter({
>                                         from: 0,
>                                         to: 90,
>                                         duration: 1000,
>                                         ease: Phaser.Math.Easing.Bounce.Out,
>                                         onUpdate: function(tween) {
>                                             _this.gemGroup.angle = tween.getValue();
>                                         },
>                                         onComplete: function() {
>                                             _this.gameArray = Phaser.Utils.Array.Matrix.RotateMatrix(_this.gameArray, -90);
>                                             for (var i = 0; i < gameOptions.fieldSize; i++) {
>                                                 for (var j = 0; j < gameOptions.fieldSize; j++) {
>                                                     _this.gameArray[i][j].gemSprite.angle += 90;
>                                                     _this.gameArray[i][j].gemSprite.x = gameOptions.gemSize * j + gameOptions.gemSize / 2 - 350;
>                                                     _this.gameArray[i][j].gemSprite.y = gameOptions.gemSize * i + gameOptions.gemSize / 2 - 350;
>                                                 }
>                                             }
>                                             _this.gemGroup.angle = 0;
>                                             _this.canPick = true;
>                                             _this.selectedGem = null;
>                                         }
>                                     });

Hi Milton,

Thank you very much. I made the changes, and was not able to get it to work. It froze. I’m using an older Dreamweaver (CS6), and it didn’t show any code errors (but CS6 is not up to date with ECMA 6, so “let” does show an error, and once an early code error displays, errors further along do not show).

I am attaching the complete, updated code in case you can see where I went wrong:

let game;
let gameOptions = {
fieldSize: 7,
gemColors: 6,
gemSize: 100,
swapSpeed: 200,
fallSpeed: 100,
destroySpeed: 200
}
const HORIZONTAL = 1;
const VERTICAL = 2;
window.onload = function() {
let gameConfig = {
width: 700,
height: 700,
scene: playGame,
}
game = new Phaser.Game(gameConfig);
window.focus()
resize();
window.addEventListener(“resize”, resize, false);
}
class playGame extends Phaser.Scene{
constructor(){
super(“PlayGame”);
}
preload(){
this.load.spritesheet(“gems”, “assets/sprites/gems.png”, {
frameWidth: gameOptions.gemSize,
frameHeight: gameOptions.gemSize
});
}
create(){
this.canPick = true;
this.dragging = false;
this.drawField();
this.selectedGem = null;
this.input.on(“pointerdown”, this.gemSelect, this);
this.input.on(“pointermove”, this.startSwipe, this);
this.input.on(“pointerup”, this.stopSwipe, this);
}
drawField(){
this.gameArray = [];
this.poolArray = [];
this.gemGroup = this.add.container(350, 350);
// this.gemGroup = this.add.group();
for(let i = 0; i < gameOptions.fieldSize; i ++){
this.gameArray[i] = [];
for(let j = 0; j < gameOptions.fieldSize; j ++){
let gem = this.add.sprite(gameOptions.gemSize * j + (gameOptions.gemSize / 2) - 350, gameOptions.gemSize * i + (gameOptions.gemSize / 2) - 350, “gems”);
//let gem = this.add.sprite(gameOptions.gemSize * j + gameOptions. gemSize / 2, gameOptions.gemSize * i + gameOptions.gemSize / 2, “gems”);
this.gemGroup.add(gem);
do{
let randomColor = Phaser.Math.Between(0, gameOptions.gemColors - 1);
gem.setFrame(randomColor);
this.gameArray[i][j] = {
gemColor: randomColor,
gemSprite: gem,
isEmpty: false
}
} while(this.isMatch(i, j));
}
}
}
isMatch(row, col){
return this.isHorizontalMatch(row, col) || this.isVerticalMatch(row, col);
}
isHorizontalMatch(row, col){
return this.gemAt(row, col).gemColor == this.gemAt(row, col - 1).gemColor && this.gemAt(row, col).gemColor == this.gemAt(row, col - 2).gemColor;
}
isVerticalMatch(row, col){
return this.gemAt(row, col).gemColor == this.gemAt(row - 1, col).gemColor && this.gemAt(row, col).gemColor == this.gemAt(row - 2, col).gemColor;
}
gemAt(row, col){
if(row < 0 || row >= gameOptions.fieldSize || col < 0 || col >= gameOptions.fieldSize){
return -1;
}
return this.gameArray[row][col];
}
gemSelect(pointer){
if(this.canPick){
this.dragging = true;
let row = Math.floor(pointer.y / gameOptions.gemSize);
let col = Math.floor(pointer.x / gameOptions.gemSize);
let pickedGem = this.gemAt(row, col)
if(pickedGem != -1){
if(this.selectedGem == null){
pickedGem.gemSprite.setScale(1.2);
pickedGem.gemSprite.setDepth(1);
this.selectedGem = pickedGem;
}
else{
if(this.areTheSame(pickedGem, this.selectedGem)){
this.selectedGem.gemSprite.setScale(1);
this.selectedGem = null;
}
else{
if(this.areNext(pickedGem, this.selectedGem)){
this.selectedGem.gemSprite.setScale(1);
this.swapGems(this.selectedGem, pickedGem, true);
}
else{
this.selectedGem.gemSprite.setScale(1);
pickedGem.gemSprite.setScale(1.2);
this.selectedGem = pickedGem;
}
}
}
}
}
}
startSwipe(pointer){
if(this.dragging && this.selectedGem != null){
let deltaX = pointer.downX - pointer.x;
let deltaY = pointer.downY - pointer.y;
let deltaRow = 0;
let deltaCol = 0;
if(deltaX > gameOptions.gemSize / 2 && Math.abs(deltaY) < gameOptions.gemSize / 4){
deltaCol = -1;
}
if(deltaX < -gameOptions.gemSize / 2 && Math.abs(deltaY) < gameOptions.gemSize / 4){
deltaCol = 1;
}
if(deltaY > gameOptions.gemSize / 2 && Math.abs(deltaX) < gameOptions.gemSize / 4){
deltaRow = -1;
}
if(deltaY < -gameOptions.gemSize / 2 && Math.abs(deltaX) < gameOptions.gemSize / 4){
deltaRow = 1;
}
if(deltaRow + deltaCol != 0){
let pickedGem = this.gemAt(this.getGemRow(this.selectedGem) + deltaRow, this.getGemCol(this.selectedGem) + deltaCol);
if(pickedGem != -1){
this.selectedGem.gemSprite.setScale(1);
this.swapGems(this.selectedGem, pickedGem, true);
// this.dragging = false; not necessary anymore
}
}
}
}
stopSwipe(){
this.dragging = false;
}
areTheSame(gem1, gem2){
return this.getGemRow(gem1) == this.getGemRow(gem2) && this.getGemCol(gem1) == this.getGemCol(gem2);
}
getGemRow(gem){
return Math.floor((gem.gemSprite.y + 350) / gameOptions.gemSize);
//return Math.floor(gem.gemSprite.y / gameOptions.gemSize);
}
getGemCol(gem){
return Math.floor(gem.gemSprite.x / gameOptions.gemSize);
}
areNext(gem1, gem2){
return Math.floor((gem.gemSprite.x + 350) / gameOptions.gemSize);
//return Math.abs(this.getGemRow(gem1) - this.getGemRow(gem2)) + Math.abs(this.getGemCol(gem1) - this.getGemCol(gem2)) == 1;
}
swapGems(gem1, gem2, swapBack){
this.swappingGems = 2;
this.canPick = false;
this.dragging = false;
let fromColor = gem1.gemColor;
let fromSprite = gem1.gemSprite;
let toColor = gem2.gemColor;
let toSprite = gem2.gemSprite;
let gem1Row = this.getGemRow(gem1);
let gem1Col = this.getGemCol(gem1);
let gem2Row = this.getGemRow(gem2);
let gem2Col = this.getGemCol(gem2);
this.gameArray[gem1Row][gem1Col].gemColor = toColor;
this.gameArray[gem1Row][gem1Col].gemSprite = toSprite;
this.gameArray[gem2Row][gem2Col].gemColor = fromColor;
this.gameArray[gem2Row][gem2Col].gemSprite = fromSprite;
this.tweenGem(gem1, gem2, swapBack);
this.tweenGem(gem2, gem1, swapBack);
}
tweenGem(gem1, gem2, swapBack){
let row = this.getGemRow(gem1);
let col = this.getGemCol(gem1);
this.tweens.add({
targets: this.gameArray[row][col].gemSprite,
x: col * gameOptions.gemSize + gameOptions.gemSize / 2 - 350,
y: row * gameOptions.gemSize + gameOptions.gemSize / 2 - 350,
//x: col * gameOptions.gemSize + gameOptions.gemSize / 2,
//y: row * gameOptions.gemSize + gameOptions.gemSize / 2,
duration: gameOptions.swapSpeed,
callbackScope: this,
onComplete: function(){
this.swappingGems --;
if(this.swappingGems == 0){
if(!this.matchInBoard() && swapBack){
this.swapGems(gem1, gem2, false);
}
else{
if(this.matchInBoard()){
this.handleMatches();
}
else{
this.canPick = true;
this.selectedGem = null;
}
}
}
}
});
}
matchInBoard(){
for(let i = 0; i < gameOptions.fieldSize; i ++){
for(let j = 0; j < gameOptions.fieldSize; j ++){
if(this.isMatch(i, j)){
return true;
}
}
}
return false;
}
handleMatches(){
this.removeMap = [];
for(let i = 0; i < gameOptions.fieldSize; i ++){
this.removeMap[i] = [];
for(let j = 0; j < gameOptions.fieldSize; j ++){
this.removeMap[i].push(0);
}
}
this.markMatches(HORIZONTAL);
this.markMatches(VERTICAL);
this.destroyGems();
}
markMatches(direction){
for(let i = 0; i < gameOptions.fieldSize; i ++){
let colorStreak = 1;
let currentColor = -1;
let startStreak = 0;
let colorToWatch = 0;
for(let j = 0; j < gameOptions.fieldSize; j ++){
if(direction == HORIZONTAL){
colorToWatch = this.gemAt(i, j).gemColor;
}
else{
colorToWatch = this.gemAt(j, i).gemColor;
}
if(colorToWatch == currentColor){
colorStreak ++;
}
if(colorToWatch != currentColor || j == gameOptions.fieldSize - 1){
if(colorStreak >= 3){
if(direction == HORIZONTAL){
console.log(“HORIZONTAL :: Length = " + colorStreak + " :: Start = (” + i + “,” + startStreak + ") :: Color = " + currentColor);
}
else{
console.log(“VERTICAL :: Length = " + colorStreak + " :: Start = (” + startStreak + “,” + i + ") :: Color = " + currentColor);
}
for(let k = 0; k < colorStreak; k ++){
if(direction == HORIZONTAL){
this.removeMap[i][startStreak + k] ++;
}
else{
this.removeMap[startStreak + k][i] ++;
}
}
}
startStreak = j;
colorStreak = 1;
currentColor = colorToWatch;
}
}
}
}
destroyGems(){
let destroyed = 0;
for(let i = 0; i < gameOptions.fieldSize; i ++){
for(let j = 0; j < gameOptions.fieldSize; j ++){
if(this.removeMap[i][j] > 0){
destroyed ++;
this.tweens.add({
targets: this.gameArray[i][j].gemSprite,
alpha: 0.5,
duration: gameOptions.destroySpeed,
callbackScope: this,
onComplete: function(){
destroyed --;
this.gameArray[i][j].gemSprite.visible = false;
this.poolArray.push(this.gameArray[i][j].gemSprite);
if(destroyed == 0){
this.makeGemsFall();
this.replenishField();
}
}
});
this.gameArray[i][j].isEmpty = true;
}
}
}
}
makeGemsFall(){
for(let i = gameOptions.fieldSize - 2; i >= 0; i --){
for(let j = 0; j < gameOptions.fieldSize; j ++){
if(!this.gameArray[i][j].isEmpty){
let fallTiles = this.holesBelow(i, j);
if(fallTiles > 0){
this.tweens.add({
targets: this.gameArray[i][j].gemSprite,
y: this.gameArray[i][j].gemSprite.y + fallTiles * gameOptions.gemSize,
duration: gameOptions.fallSpeed * fallTiles
});
this.gameArray[i + fallTiles][j] = {
gemSprite: this.gameArray[i][j].gemSprite,
gemColor: this.gameArray[i][j].gemColor,
isEmpty: false
}
this.gameArray[i][j].isEmpty = true;
}
}
}
}
}
holesBelow(row, col){
let result = 0;
for(let i = row + 1; i < gameOptions.fieldSize; i ++){
if(this.gameArray[i][col].isEmpty){
result ++;
}
}
return result;
}
replenishField(){
let replenished = 0;
for(let j = 0; j < gameOptions.fieldSize; j ++){
let emptySpots = this.holesInCol(j);
if(emptySpots > 0){
for(let i = 0; i < emptySpots; i ++){
replenished ++;
let randomColor = Phaser.Math.Between(0, gameOptions.gemColors - 1);
this.gameArray[i][j].gemColor = randomColor;
this.gameArray[i][j].gemSprite = this.poolArray.pop()
this.gameArray[i][j].gemSprite.setFrame(randomColor);
this.gameArray[i][j].gemSprite.visible = true;
this.gameArray[i][j].gemSprite.x = gameOptions.gemSize * j + gameOptions.gemSize / 2 - 350;
this.gameArray[i][j].gemSprite.y = gameOptions.gemSize / 2 - (emptySpots - i) * gameOptions.gemSize - 350;
//this.gameArray[i][j].gemSprite.x = gameOptions.gemSize * j + gameOptions.gemSize / 2;
//this.gameArray[i][j].gemSprite.y = gameOptions.gemSize / 2 - (emptySpots - i) * gameOptions.gemSize;
this.gameArray[i][j].gemSprite.alpha = 1;
this.gameArray[i][j].isEmpty = false;
this.tweens.add({
targets: this.gameArray[i][j].gemSprite,
y: gameOptions.gemSize * i + gameOptions.gemSize / 2 - 350,
//y: gameOptions.gemSize * i + gameOptions.gemSize / 2,
duration: gameOptions.fallSpeed * emptySpots,
callbackScope: this,
onComplete: function(){
replenished --;
if(replenished == 0){
if(this.matchInBoard()){
this.time.addEvent({
delay: 250,
callback: this.handleMatches()
});
}
else{
this.gameArray = Phaser.Utils.Array.Matrix.RotateMatrix(this.gameArray, -90);
for (var i = 0; i < gameOptions.fieldSize; i++) {
for (var j = 0; j < gameOptions.fieldSize; j++) {
this.gameArray[i][j].gemSprite.x = gameOptions.gemSize * j + gameOptions.gemSize / 2;
this.gameArray[i][j].gemSprite.y = gameOptions.gemSize * i + gameOptions.gemSize / 2;
}
}
var _this = this;

                                 this.tweens.addCounter({

                                     from: 0,

                                     to: 90,

                                     duration: 1000,

                                     ease: Phaser.Math.Easing.Bounce.Out,

                                     onUpdate: function(tween) {

                                         _this.gemGroup.angle = tween.getValue();

                                     },

                                     onComplete: function() {

                                         _this.gameArray = Phaser.Utils.Array.Matrix.RotateMatrix(_this.gameArray, -90);

                                         for (var i = 0; i < gameOptions.fieldSize; i++) {

                                             for (var j = 0; j < gameOptions.fieldSize; j++) {

                                                 _this.gameArray[i][j].gemSprite.angle += 90;

                                                 _this.gameArray[i][j].gemSprite.x = gameOptions.gemSize * j + gameOptions.gemSize / 2 - 350;

                                                 _this.gameArray[i][j].gemSprite.y = gameOptions.gemSize * i + gameOptions.gemSize / 2 - 350;

                                             }

                                         }

                                         _this.gemGroup.angle = 0;

                                         _this.canPick = true;

                                         _this.selectedGem = null;

                                     }

                                 });

								//this.canPick = true;
                                //this.selectedGem = null;
                            }
                        }
                    }
                });
            }
        }
    }
}
holesInCol(col){
    var result = 0;
    for(let i = 0; i < gameOptions.fieldSize; i ++){
        if(this.gameArray[i][col].isEmpty){
            result ++;
        }
    }
    return result;
}

}function resize() {
var canvas = document.querySelector(“canvas”);
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var windowRatio = windowWidth / windowHeight;
var gameRatio = game.config.width / game.config.height;
if(windowRatio < gameRatio){
canvas.style.width = windowWidth + “px”;
canvas.style.height = (windowWidth / gameRatio) + “px”;
}
else{
canvas.style.width = (windowHeight * gameRatio) + “px”;
canvas.style.height = windowHeight + “px”;
}
}

//not sure why this is formatted in tis posting differently than in my code editor?

You can just download the correct version here.

You have left some old code between else and var _this = this;

Thank you again. Apologies for the code error. This works beautifully. Have learned a lot.

Next, I’ll see if I can set a variable for the angle (-90) with a random generator so that it will go -90, -180, -270, maybe a full turn, -360, and +90, +180, +270, maybe +360, again a full turn. Also may add scoring (let score) which I suspect can be placed at the code location of a match to increment as score++.

These hopefully would make it more challenging. The goal is to maintain user interest so they practice this as a mental rotation/stress buster exercise.

Thank you!

Probably a stupid questions but gems aren’t displaying.
Code is fine, path is fine, gems.png is there.
Is it my browser?
Thanks

I’m not sure which version you’re using, but this one should work.
You can open the Developer Tools console to see if there are errors (Ctrl-Shift-J for Chrome, K for Firefox).

Keith,

I am not sure if the problem you are having occurs when you are trying to view the source files locally? I initially had a problem with the gems.png displaying and found it had to do with a FireFox local setting. If this is the problem, then you would need to change the local setting for your browser (in about:config for FF) in order to be able to view the gems. See this FF link for the FF fix. HTH if this is the problem.

Best Wishes,