I used to play this, when the Internet was not invented. So it’s about time I created my own version. It’s the most complex example of MaxCanvas todate, and I’ll readily admit to not having tidied the code up (or commenting it) too much. The data structures are a bit clunky and hard to follow, but … it works, is playable and I’m happy.
Code
// MaxCanvas Bases Maj-Jong Tile match implementation // Started March 2020 - whenever 8-) // // Required to drag MC.js into Intellisense // thanks to https://visualstudiomagazine.com/blogs/tool-tracker/2018/07/use-javascript-code.aspx (Dec 2018) /// <reference path="MC.js" /> // Gamecode is wrapped in .. window.onload = (function () { -- ALL CODE HERE --}); .. so game will start AFTER everything else is loaded window.onload = (function() { // AQUIRE HTML DOM CANVAS REFERENCE // HTML has to have a HTML5 canvas element tagged as "canvas" var c=document.getElementById("canvas"); ////////////////////////// // INITIALISE MC Engine // ////////////////////////// // CHOOSE a canvas size option // // CHOICE 1: Canvas Size fixed by HTML MC.init(c); // CHOICE 2: Full Window Option (Canvas will be resized to fit window) //MC.init(c,"fullWindow"); // Configure .game MC.game.gravity.set(0,100); // gravity this game MC.game.physicsUpdates = 1; // Simple example, no need for multiple physics Updates /////////////////////////// // END OF Initialisation // /////////////////////////// //----------------------------------------------------------------------------- ////////////////////////// // VARIABLE DECLARATION // ////////////////////////// var tilesheet = new MC.Picture("Images/MahjongTiles.png"); var effectSheet = new MC.Picture("Images/Freeze.png"); var backdrop = new MC.Picture("Images/blue-grunge-starburst.jpg") var shadow = new MC.Color(121, 121, 121, 0.7); var Effects = new MC.SpriteBin(); EffectsFactory = function (position, duration) { var Effect = new MC.Sprite({ type: "anim", picture: effectSheet, scale: 0.75, pos: position, angle: MC.maths.randBetween(0,360), angleVel: MC.maths.randBetween(-360,360) }); Effect.setAnimation(10, 10, 0, 0, 5, 8, 20); Effect.kill(duration); Effects.push(Effect); } var Scales = new Object({ dx: 48, dy: 60, dyy: 6 }); function TileData( x, y, match, number, name) { this.x = x; this.y = y; this.match = match; this.number = number; this.name = name; }; var AllTileData = new Array ( new TileData(0,0,1,1,"Flower-Plum"), new TileData(1,0,1,1,"Flower-Orchid"), new TileData(2,0,1,1,"Flower-Chrys"), new TileData(3,0,1,1,"Flower-Bamboo"), new TileData(4,0,2,1,"Flower-Summer"), new TileData(5,0,2,1,"Flower-Winter"), new TileData(6,0,2,1,"Flower-Fall"), new TileData(7,0,2,1,"Flower-Spring"), new TileData(0,1,3,4,"Dragon-Red"), new TileData(1,1,4,4,"Dragon-Green"), new TileData(2,1,5,4,"Dragon-White"), new TileData(3,1,6,4,"Wind-North"), new TileData(4,1,7,4,"Wind-South"), new TileData(5,1,8,4,"Wind-East"), new TileData(6,1,9,4,"Wind-West"), new TileData(7,1,10,4,"Crak-One"), new TileData(0, 2, 11, 4, "Crak-Two"), new TileData(1, 2, 12, 4, "Crak-Three"), new TileData(2, 2, 13, 4, "Crak-Four"), new TileData(3, 2, 14, 4, "Crak-Five"), new TileData(4, 2, 15, 4, "Crak-Six"), new TileData(5, 2, 16, 4, "Crak-Seven"), new TileData(6, 2, 17, 4, "Crak-Eight"), new TileData(7, 2, 18, 4, "Crak-Nine"), new TileData(0,3,19,4,"Bamboo-One"), new TileData(1,3,20,4,"Bamboo-Two"), new TileData(2,3,21,4,"Bamboo-Three"), new TileData(3,3,22,4,"Bamboo-Four"), new TileData(4,3,23,4,"Bamboo-Five"), new TileData(5,3,24,4,"Bamboo-Six"), new TileData(6,3,25,4,"Bamboo-Seven"), new TileData(7,3,26,4,"Bamboo-Eight"), new TileData(0,4,27,4,"Bamboo-Nine"), new TileData(1,4,28,4,"Coin-One"), new TileData(2,4,29,4,"Coin-Two"), new TileData(3,4,30,4,"Coin-Three"), new TileData(4,4,31,4,"Coin-Four"), new TileData(5,4,32,4,"Coin-Five"), new TileData(6,4,33,4,"Coin-Six"), new TileData(7,4,34,4,"Coin-Seven"), new TileData(0,5,35,4,"Coin-Eight"), new TileData(1,5,36,4,"Coin-Nine"), new TileData(2,5,37,0,"Blank"), new TileData(3,5,38,0,"Red"), new TileData(4,5,38,0,"Green"), new TileData(5,5,39,0,"Blue"), new TileData(6,5,40,0,"Yellow"), new TileData(7,5,41,0,"Mask") ); var Deck = new Array(); for (var i = 0; i < AllTileData.length; i++) { for (var j = 0; j < AllTileData[i].number; j++) { Deck.push(i); } } ShuffleDeck = function () { Deck = MC.utils.shuffle(Deck); } // |-0-|-1-|-2-|-3-|-4-|-5-|-6-|-7-|-8-|-9-|10-|11-| // |12-|13-|14-|15-|16-|17-|18-|19-| // |20-|21-|22-|23-|24-|25-|26-|27-|28-|29-| // |30-|31-|32-|33-|34-|35-|36-|37-|38-|39-|40-|41-| //|42-| |43-|44-| // |45-|46-|47-|48-|49-|50-|51-|52-|53-|54-|55-|56-| // |57-|58-|59-|60-|61-|62-|63-|64-|65-|66-| // |67-|68-|69-|70-|71-|72-|73-|74-| // |75-|76-|77-|78-|79-|80-|81-|82-|83-|84-|85-|86-| // // |87-|88-|89-|90-|91-|92-| // |93-|94-|95-|96-|97-|98-| // |99-|100|101|102|103|104| // |105|106|107|108|109|110| // |111|112|113|114|115|116| // |117|118|119|120|121|122| // // |123|124|125|126| // |127|128|129|130| // |131|132|133|134| // |135|136|137|138| // // |139|140| // |141|142| // // |143| // we need to draw these in ordr of tiers (in all shadow then all tiles) order var DrawOrder = [ [0,86], [87,122], [123,138], [139,142], [143,143] ]; var grid1 = new Array( // TIER ONE [-5.5, 3.5, 1, -1, 1, -1], [-4.5, 3.5, 1, 0, 2, -1], [-3.5, 3.5, 1, 1, 3, -1], [-2.5, 3.5, 1, 2, 4, -1], [-1.5, 3.5, 1, 3, 5, -1], [-0.5, 3.5, 1, 4, 6, -1], [ 0.5, 3.5, 1, 5, 7, -1], [1.5, 3.5, 1, 6, 8, -1], [2.5, 3.5, 1, 7, 9, -1], [3.5, 3.5, 1, 8, 10, -1], [4.5, 3.5, 1, 9, 11, -1], [5.5, 3.5, 1, 10, -1, -1], [-3.5,2.5,1,-1,12,-1],[-2.5,2.5,1,12,14,87],[-1.5,2.5,1,13,15,88],[-0.5,2.5,1,14,16,89], [0.5,2.5,1,15,17,90],[1.5,2.5,1,16,18,91],[2.5,2.5,1,17,19,92],[3.5,2.5,1,18,-1,-1], [-4.5,1.5,1,-1,21,-1],[-3.5,1.5,1,20,22,-1],[-2.5,1.5,1,21,23,93],[-1.5,1.5,1,22,24,94],[-0.5,1.5,1,23,25,95], [0.5,1.5,1,24,26,96],[1.5,1.5,1,25,27,97],[2.5,1.5,1,26,28,98],[3.5,1.5,1,27,29,-1],[4.5,1.5,1,28,-1,-1], [-5.5,0.5,1,42,31,-1],[-4.5,0.5,1,30,32,-1],[-3.5,0.5,1,31,33,-1],[-2.5,0.5,1,32,34,99],[-1.5,0.5,1,33,35,100],[-0.5,0.5,1,34,36,101], [0.5,0.5,1,35,37,102],[1.5,0.5,1,36,38,103],[2.5,0.5,1,37,39,104],[3.5,0.5,1,38,40,-1],[4.5,0.5,1,39,41,-1],[5.5,0.5,1,40,43,-1], [-6.5,0,1,-1,30,45],[6.5,0,1,41,44,56],[7.5,0,1,43,-1,-1], [-5.5,-0.5,1,42,46,-1],[-4.5,-0.5,1,45,47,-1],[-3.5,-0.5,1,46,48,-1],[-2.5,-0.5,1,47,49,105],[-1.5,-0.5,1,48,50,106],[-0.5,-0.5,1,49,51,107], [0.5,-0.5,1,50,52,108],[1.5,-0.5,1,51,53,109],[2.5,-0.5,1,52,54,110],[3.5,-0.5,1,53,55,-1],[4.5,-0.5,1,54,56,-1],[5.5,-0.5,1,55,43,-1], [-4.5,-1.5,1,-1,58,-1],[-3.5,-1.5,1,57,59,-1],[-2.5,-1.5,1,58,60,111],[-1.5,-1.5,1,59,61,112],[-0.5,-1.5,1,60,62,113], [0.5,-1.5,1,61,63,114],[1.5,-1.5,1,62,64,115],[2.5,-1.5,1,63,65,116],[3.5,-1.5,1,64,66,-1],[4.5,-1.5,1,65,-1,-1], [-3.5,-2.5,1,-1,68,-1],[-2.5,-2.5,1,67,69,117],[-1.5,-2.5,1,68,70,118],[-0.5,-2.5,1,69,71,119], [0.5,-2.5,1,70,72,120],[1.5,-2.5,1,71,73,121],[2.5,-2.5,1,72,74,122],[3.5,-2.5,1,73,-1,-1], [-5.5, -3.5, 1, -1, 76, -1], [-4.5, -3.5, 1, 75, 77, -1], [-3.5, -3.5, 1, 76, 78, -1], [-2.5, -3.5, 1, 77, 79, -1], [-1.5, -3.5, 1, 78, 80, -1], [-0.5, -3.5, 1, 79, 81, -1], [ 0.5, -3.5, 1, 80, 82, -1], [1.5, -3.5, 1, 81, 83, -1], [2.5, -3.5, 1, 82, 84, -1], [3.5, -3.5, 1, 83, 85, -1], [4.5, -3.5, 1, 84, 86, -1], [5.5, -3.5, 1, 85, -1, -1], // TIER TWO [-2.5, 2.5, 2, -1, 88, -1], [-1.5, 2.5, 2, 87, 89, -1],[-0.5, 2.5, 2, 88, 90, -1], [0.5, 2.5, 2, 89, 91, -1], [1.5, 2.5, 2, 90, 92, -1],[2.5, 2.5, 2, 91, -1, -1], [-2.5, 1.5, 2, -1, 94, -1], [-1.5, 1.5, 2, 93, 95, 123],[-0.5, 1.5, 2, 94, 96, 124], [0.5, 1.5, 2, 95, 97, 125], [1.5, 1.5, 2, 96, 98, 126],[2.5, 1.5, 2, 97, -1, -1], [-2.5, 0.5, 2, -1, 100, -1], [-1.5, 0.5, 2, 99, 101, 127],[-0.5, 0.5, 2, 100, 102, 128], [0.5, 0.5, 2, 101, 103, 129], [1.5, 0.5, 2, 102, 104, 130],[2.5, 0.5, 2, 103, -1, -1], [-2.5, -0.5, 2, -1, 106, -1], [-1.5, -0.5, 2, 105, 107, 131],[-0.5, -0.5, 2, 106, 108, 132], [0.5, -0.5, 2, 107, 109, 133], [1.5, -0.5, 2, 108, 110, 134],[2.5, -0.5, 2, 109, -1, -1], [-2.5, -1.5, 2, -1, 112, -1], [-1.5, -1.5, 2, 111, 113, 135],[-0.5, -1.5, 2, 112, 114, 136], [0.5, -1.5, 2, 113, 115, 137], [1.5, -1.5, 2, 114, 116, 138],[2.5, -1.5, 2, 115, -1, -1], [-2.5, -2.5, 2, -1, 118, -1], [-1.5, -2.5, 2, 117, 119, -1],[-0.5, -2.5, 2, 118, 120, -1], [0.5, -2.5, 2, 119, 121, -1], [1.5, -2.5, 2, 120, 122, -1],[2.5, -2.5, 2, 121, -1, -1], // TIER THREE [-1.5,1.5,3,-1,124,-1],[-0.5,1.5,3,123,125,-1],[0.5,1.5,3,124,126,-1],[1.5,1.5,3,125,-1,-1], [-1.5,0.5,3,-1,128,-1],[-0.5,0.5,3,127,129,139],[0.5,0.5,3,128,130,140],[1.5,0.5,3,129,-1,-1], [-1.5,-0.5,3,-1,132,-1],[-0.5,-0.5,3,131,133,141],[0.5,-0.5,3,132,134,142],[1.5,-0.5,3,133,-1,-1], [-1.5,-1.5,3,-1,136,-1],[-0.5,-1.5,3,135,137,-1],[0.5,-1.5,3,136,138,-1],[1.5,-1.5,3,137,-1,-1], // TIER FOUR [-0.5,0.5,4,-1,140,143],[0.5,0.5,4,139,-1,143], [-0.5,-0.5,4,-1,142,143],[0.5,-0.5,4,141,-1,143], // TIER FIVE [0,0,5,-1,-1,-1] ); function GridData(x,y, tier,left,right,upper) { this.offX = x; this.offY = y; this.tier = tier; this.left = left; this.right = right; this.upper = upper; }; TileFactory = function (TileDataIndex) { T = new MC.Sprite({ type: "anim", picture: tilesheet, pos: new MC.Point(MC.canvas.midX, MC.canvas.midY), scale: 0.75 }); T.data = AllTileData[TileDataIndex]; T.setAnimation(8, 6, T.data.x, T.data.y, T.data.x, T.data.y, 99999); return T; }; var GameGrid = new Array(); GameGrid.update = function () { for (var i = 0; i < 144; i++) { GameGrid[i].Tile.update(); } }; GameGrid.render = function () { for (var Tier = 0; Tier < DrawOrder.length; Tier++) { // draw shadows for (var i = DrawOrder[Tier][0]; i <= DrawOrder[Tier][1]; i++) { if(GameGrid[i].alive) MC.draw.rectBasic(GameGrid[i].Tile.pos.clone().add(-7, 7), GameGrid[i].Tile.size1 / 8 * GameGrid[i].Tile.scale, GameGrid[i].Tile.size2 / 6 * GameGrid[i].Tile.scale, Tier > 0 ? shadow : "black"); } // draw tiles for (var i = DrawOrder[Tier][0]; i <= DrawOrder[Tier][1]; i++) { if (GameGrid[i].alive) GameGrid[i].Tile.render(); } } if (GameGrid.Selected != null) { GameGrid[GameGrid.Selected].Tile.drawBB("orange"); /* // CHEAT FUNCTION. Saved for dev purposes (highlights tile matches to the currently selected one for (var i = 0; i < 144; i++) { if (GameGrid.Selected != i && GameGrid[i].alive && GameGrid[i].free() && GameGrid[i].type == GameGrid[GameGrid.Selected].type) { GameGrid[i].Tile.drawBB("red"); } } */ } }; GameGrid.clear = function () { GameGrid.length = 0; } GameGrid.Selected = null; GameGrid.OnClick = function () { var m = MC.mouse.pos.clone(); var onGrid = false; for (var i = 0; i < 144; i++) { if (GameGrid[i].Tile.hit(m)) { if (GameGrid[i].alive) onGrid = true; var free = GameGrid[i].free(); // Null, select if legal if (GameGrid.Selected == null && free == true) { GameGrid.Selected = i; } // Deselect if required else if (GameGrid.Selected == i) { GameGrid.Selected = null; } // maka a match else if (i != GameGrid.Selected && free && GameGrid[i].type == GameGrid[GameGrid.Selected].type) { EffectsFactory(GameGrid[i].Tile.pos, 0.3); EffectsFactory(GameGrid[GameGrid.Selected].Tile.pos, 0.3); GameGrid[i].alive = GameGrid[GameGrid.Selected].alive = false; Moves.addToHistory( i, GameGrid.Selected ); GameGrid.Selected = null; Moves.Calculate(); } // Deselect AND Reselect else if (i != GameGrid.Selected && free == true) { GameGrid.Selected = i; } } } if (!onGrid) GameGrid.Selected = null; }; GameGrid.Shuffle = function () { var positions = []; var tileNumbers = []; for (var i = 0; i < 144; i++) { if (GameGrid[i].alive) { positions.push(i); tileNumbers.push(GameGrid[i].tileNumber); } } tileNumbers = MC.utils.shuffle(tileNumbers); var len = positions.length; for (var i = 0; i < len; i++) { GameGrid[positions[i]] = ConfiguredTileFactory( positions[i], tileNumbers[i] ); } Moves.Calculate(); Moves.newGame(); GOCon.reset(); }; ConfiguredTileFactory = function ( GridPosition, TileNumber ) { var g = new Object(); g.Tile = TileFactory(TileNumber); g.tileNumber = TileNumber; g.gridPosition = GridPosition; g.type = g.Tile.data.match; g.name = g.Tile.data.name; g.offX = grid1[GridPosition][0]; g.offY = grid1[GridPosition][1]; g.tier = grid1[GridPosition][2]; g.Tile.pos.x = MC.canvas.midX + (g.offX * Scales.dx); g.Tile.pos.y = MC.canvas.midY + (g.offY * Scales.dy * -1) - (g.tier * Scales.dyy); g.leftTile = grid1[GridPosition][3]; g.rightTile = grid1[GridPosition][4]; g.upperTile = grid1[GridPosition][5]; g.selected = false; g.alive = true; g.free = function () { if (!this.alive) return false; switch (this.gridPosition) { // offX,offY, tier, left, right, upper // 42 - [-6.5,0,1,-1,30,45], 43 - [6.5,0,1,41,44,56], 44 - [7.5,0,1,43,-1,-1], case 42: return true; case 43: if (GameGrid[44].alive == false || (GameGrid[41].alive == false && GameGrid[56].alive == false)) { return true; } else { return false; } case 44: return true; default: if (this.upperTile != -1) { if (GameGrid[this.upperTile].alive == true) return false; } if (this.rightTile == -1 || this.leftTile == -1) return true; if (this.rightTile != -1 && GameGrid[this.rightTile].alive == false) return true; if (this.leftTile != -1 && GameGrid[this.leftTile].alive == false) return true; return false; } }; return g; } Reset = function () { ShuffleDeck(); GameGrid.clear(); for (var i = 0; i < 144; i++) { GameGrid.push(ConfiguredTileFactory(i, Deck[i])); GameGrid[i].alive = true; } GameGrid.Selected = null; Moves.newGame(); Moves.Calculate(); Timer.start(); EndGameController.stop(); } var Moves = { Available: 0, OptionsArray: null, // Tile.Type ranges from 1-36 .. using a 37 length Array, to prevent head bending later Clear: function () { this.OptionsArray = new Array(37); for (var i = 0; i < 37; i++) this.OptionsArray[i] = new Array(0); }, Calculate: function() { this.Clear(); this.Available = 0; var aliveTiles = 0; for (var i = 0; i < 144; i++) { if (GameGrid[i].alive == true) aliveTiles++; if ( GameGrid[i].free() ) { this.OptionsArray[GameGrid[i].type].push( i ); switch ( this.OptionsArray[GameGrid[i].type].length ) { case 0: case 1: break; case 2: this.Available += 1; break; case 3: this.Available += 2; break; case 4: this.Available += 3; break; default: break; } } } MoveDisplay.setValues({text: " Moves: "+ this.Available + " "}); // Check for end Game if (this.Available == 0) { var message = aliveTiles == 0 ? "Well Done !! New Game?" : "No Moves ... New Game?"; GOCon.start(message); Timer.pause(); if (aliveTiles == 0) EndGameController.start(); } }, history: [], addToHistory: function ( position1, position2 ) { this.history.push(new MC.Point(position1, position2)); }, newGame: function() { this.history = []; }, undo: function() { if (this.history.length > 0) { var last = this.history.pop(); GameGrid[last.x].alive = true; GameGrid[last.y].alive = true; } }, restart: function() { for (var i = 0; i < 144; i++) GameGrid[i].alive = true; }, hint: function () { var moves = []; for (var i = 0; i < this.OptionsArray.length; i++) { if( this.OptionsArray[i].length >=2){ moves.push(i); } } if (moves.length > 0) { moves = MC.utils.shuffle(moves); var index = moves[0]; this.OptionsArray[index] = MC.utils.shuffle(this.OptionsArray[index]); var t1 = this.OptionsArray[index][0]; var t2 = this.OptionsArray[index][1]; EffectsFactory(GameGrid[t1].Tile.pos, 1); EffectsFactory(GameGrid[t2].Tile.pos, 1); } } }; // of course, we need a piece of fun for the end game var EndGame = new MC.SpriteBin(); var EndGameController = { running: false, time: 0, spriteCount: 80, update: function () { if (this.running == true) { if (EndGame.bin.length < this.spriteCount) { this.time += MC.game.deltaTime; if (this.time > 0.2) { this.time = 0; var tile = TileFactory(MC.maths.randBetween(0, AllTileData.length - 7)); tile.setValues({ edgeBounce: true, gravity: true, pos: new MC.Point(MC.maths.randBetween(40, MC.canvas.width - 40), 40), vel: new MC.Point(MC.maths.randBetween(-80, 80), 0), angleVel: MC.maths.randBetween( -360,360 ) }); EndGame.push(tile); } } EndGame.update(); } }, stop: function () { EndGame.empty(); this.running = false; }, start: function () { EndGame.empty(); this.running = true; }, render: function () { EndGame.render(); } }; var leftGUI = new MC.GUI(); // GUI = holder for Control Boxes (MC.ConBox) var rightGUI = new MC.GUI(); var ResetButton = new MC.ConBox({ width: 120, height: 40, text: "New Game (n)", onClick: function () { Reset(); GOCon.reset(); } }); var Shuffle = new MC.ConBox({ width: 120, height: 40, text: " Shuffle (s) ", onClick: function () { GameGrid.Shuffle(); EndGameController.stop(); } }); var Undo = new MC.ConBox({ width: 120, height: 40, text: " Undo (u) ", onClick: function () { Moves.undo(); GOCon.reset(); EndGameController.stop(); } }); var Restart = new MC.ConBox({ width: 120, height: 40, text: " Restart (r) ", onClick: function () { Moves.restart(); GOCon.reset(); EndGameController.stop(); Timer.start(); } }); var Hint = new MC.ConBox({ width: 120, height: 40, text: " Hint (h) ", onClick: function () { Moves.hint(); } }); // using this an an inert Con Box, for display purposes var MoveDisplay = new MC.ConBox({ width: 120, height: 40, text: " Moves: " }); var TimeDisplay = MoveDisplay.clone(); // n.b. text will be set by Timer leftGUI.push(ResetButton, Restart, Shuffle, Undo, Hint); // Add the ConBoxes to the GUI rightGUI.push(MoveDisplay, TimeDisplay); // special ConBox, modified to bounce in at end game, user Timer and the NEW MC.Utils.tween methods var GameOver = new MC.ConBox({ width: 300, height: 70, text: "Well Done!! New Game?", pos: new MC.Point(MC.canvas.midX, 0 - this.height), onClick: function () { GOCon.onClick(); } // defer click method to the Controller }); GOCon = { // GameOver Controller box: GameOver, size: new MC.Point(GameOver.width, GameOver.height), positions: [ new MC.Point(MC.canvas.midX, 0 - (2.5 * GameOver.height)), new MC.Point(MC.canvas.midX, MC.canvas.height - GameOver.height), new MC.Point(MC.canvas.midX, MC.canvas.height + GameOver.height)], times: [ 2, 1 ], animationTime: 0, running: false, comingIn: true, onClick: function () { if (!this.running) { this.running = true; Reset(); } }, reset: function () { this.box.setValues({ width: this.size.x, height: this.size.y, pos: this.positions[0] }); this.running = false; this.comingIn = true; }, start: function (message) { this.reset(); if (message != undefined) this.box.setValues({ text: message }); this.running = true; animationTime = 0; }, update: function () { if (this.running == true) { this.animationTime += MC.game.deltaTime; var mod = this.comingIn == true ? 0 : 1; var finished = this.animationTime >= this.times[mod]; var tween = this.animationTime / this.times[mod]; this.box.setValues({ pos: MC.maths.lerp(this.positions[mod], this.positions[mod + 1], tween), }); if (this.comingIn == true) { this.box.setValues({ width: this.size.x * (tween + 0.1), height: this.size.y * (tween + 0.1) }); } if (finished == true) { if (this.comingIn == false) this.reset(); else { this.running = false; this.comingIn = false; this.animationTime = 0; } } } else { this.box.update(); } } } var Timer = { running: false, elapsed: 0, start: function () { this.running = true; this.elapsed = 0; this.updateLabel(); }, pause: function () { this.running = false; }, update: function () { if (this.running == true) { this.elapsed += MC.game.deltaTime; this.updateLabel(); } }, updateLabel: function () { // credit to https://stackoverflow.com/questions/6312993/javascript-seconds-to-time-string-with-format-hhmmss (Apr 2020) var sec_num = parseInt(this.elapsed, 10); var hours = Math.floor(sec_num / 3600); var minutes = Math.floor((sec_num - (hours * 3600)) / 60); var seconds = sec_num - (hours * 3600) - (minutes * 60); if (hours < 10) { hours = "0" + hours; } if (minutes < 10) { minutes = "0" + minutes; } if (seconds < 10) { seconds = "0" + seconds; } TimeDisplay.setValues({ text: hours + ':' + minutes + ':' + seconds }); } }; MC.keys.n.onUp = function () { Reset(); GOCon.reset(); }; MC.keys.s.onUp = function () { GameGrid.Shuffle(); }; MC.keys.r.onUp = function () { Restart.onClick(); }; MC.keys.u.onUp = function () { Undo.onClick(); }; MC.keys.h.onUp = function () { Hint.onClick(); }; MC.mouse.onClickL = function () { // configure MC.game so it checks the GUI upon Left click down GameGrid.OnClick(); leftGUI.click(); GameOver.checkClick(); // Feel free to add further code for Left Clicks here }; ////////////////////////// // END OF declarations // ////////////////////////// ////////////////////////// // Game Initialisation // ////////////////////////// Reset(); GOCon.reset(); ////////////////////////// // GAME LOOP // ////////////////////////// // first call, request subsequently made within gameLoop(); function gameLoop() { // ESSENTIAL MAINTAINANCE requestAnimationFrame(gameLoop); // Required so the PC will automatically call the gameLoop again when (1) the screen is ready and (2) the CPU has finished any previous frame calculations MC.game.update(); // Essential to update MC.game to ensure MC.game.deltaTime is available // PHYSICS UPDATE LOOP .. for (var i = 0; i < MC.game.physicsUpdates; i++) { EndGameController.update(); // Add Further Sprite and SpriteBin updates here } // NON-PHYSICS UPDATES GameGrid.update(); leftGUI.update(); GOCon.update(); Effects.update(); Timer.update(); // RENDER // Stage 1. Clear the canvas MC.draw.clearCanvas("Black"); // Stage 2. Render Sprites backdrop.render(new MC.Point(MC.canvas.midX, MC.canvas.midY), 0, MC.canvas.width, MC.canvas.height); GameGrid.render(); Effects.render(); EndGameController.render(); // Stage 3. Render GUIs (and moves) leftGUI.render(0, 0); rightGUI.render(MC.canvas.right - 140, 0); GameOver.render(); } }); // EoF