match3game-toembal
Version:
Match3Game is an engaging and colorful match-3 puzzle game.
466 lines (413 loc) • 11.1 kB
JavaScript
class Game {
constructor(size, typesOfTiles) {
this.size = size;
this.typesOfTiles = typesOfTiles;
this.board = [];
this.score = 0;
this.initBoard();
}
initBoard() {
for (let i = 0; i < this.size; i++) {
this.board[i] = [];
for (let j = 0; j < this.size; j++) {
this.board[i][j] = this.randomTile();
}
}
}
randomTile() {
return Math.floor(Math.random() * this.typesOfTiles);
}
swapTiles(x1, y1, x2, y2) {
let temp = this.board[x1][y1];
this.board[x1][y1] = this.board[x2][y2];
this.board[x2][y2] = temp;
this.checkForMatches();
}
checkForMatches() {
let matches = [];
for (let i = 0; i < this.size; i++) {
for (let j = 0; j < this.size; j++) {
if (this.isMatch(i, j)) {
matches.push({x: i, y: j});
}
}
}
if (matches.length > 0) {
this.removeMatches(matches);
this.score += matches.length;
this.fillSpaces();
this.checkForMatches();
}
}
isMatch(x, y) {
let tile = this.board[x][y];
return this.checkDirection(x, y, -1, 0, tile) ||
this.checkDirection(x, y, 1, 0, tile) ||
this.checkDirection(x, y, 0, -1, tile) ||
this.checkDirection(x, y, 0, 1, tile);
}
checkDirection(x, y, dx, dy, tile) {
let count = 1;
let nx = x + dx;
let ny = y + dy;
while (nx >= 0 && nx < this.size && ny >= 0 && ny < this.size && this.board[nx][ny] === tile) {
count++;
nx += dx;
ny += dy;
}
return count >= 3;
}
removeMatches(matches) {
matches.forEach(match => {
this.board[match.x][match.y] = this.randomTile();
});
}
fillSpaces() {
for (let i = 0; i < this.size; i++) {
for (let j = 0; j < this.size; j++) {
if (this.board[i][j] === null) {
this.board[i][j] = this.randomTile();
}
}
}
}
}
let game = new Game(8, 5);
const Tween = {
linear: function (t, b, c, d) {
return (c * t) / d + b;
},
easeIn: function (t, b, c, d) {
return c * (t /= d) * t + b;
},
easeOut: function (t, b, c, d) {
return -c * (t /= d) * (t - 2) + b;
},
easeBoth: function (t, b, c, d) {
if ((t /= d / 2) < 1) {
return (c / 2) * t * t + b;
}
return (-c / 2) * (--t * (t - 2) - 1) + b;
},
easeInStrong: function (t, b, c, d) {
return c * (t /= d) * t * t * t + b;
},
easeOutStrong: function (t, b, c, d) {
return -c * ((t = t / d - 1) * t * t * t - 1) + b;
},
easeBothStrong: function (t, b, c, d) {
if ((t /= d / 2) < 1) {
return (c / 2) * t * t * t * t + b;
}
return (-c / 2) * ((t -= 2) * t * t * t - 2) + b;
},
elasticIn: function (t, b, c, d, a, p) {
if (t === 0) {
return b;
}
if ((t /= d) == 1) {
return b + c;
}
if (!p) {
p = d * 0.3;
}
if (!a || a < Math.abs(c)) {
a = c;
var s = p / 4;
} else {
var s = (p / (2 * Math.PI)) * Math.asin(c / a);
}
return (
-(
a *
Math.pow(2, 10 * (t -= 1)) *
Math.sin(((t * d - s) * (2 * Math.PI)) / p)
) + b
);
},
elasticOut: function (t, b, c, d, a, p) {
if (t === 0) {
return b;
}
if ((t /= d) == 1) {
return b + c;
}
if (!p) {
p = d * 0.3;
}
if (!a || a < Math.abs(c)) {
a = c;
var s = p / 4;
} else {
var s = (p / (2 * Math.PI)) * Math.asin(c / a);
}
return (
a * Math.pow(2, -10 * t) * Math.sin(((t * d - s) * (2 * Math.PI)) / p) +
c +
b
);
},
elasticBoth: function (t, b, c, d, a, p) {
if (t === 0) {
return b;
}
if ((t /= d / 2) == 2) {
return b + c;
}
if (!p) {
p = d * (0.3 * 1.5);
}
if (!a || a < Math.abs(c)) {
a = c;
var s = p / 4;
} else {
var s = (p / (2 * Math.PI)) * Math.asin(c / a);
}
if (t < 1) {
return (
-0.5 *
(a *
Math.pow(2, 10 * (t -= 1)) *
Math.sin(((t * d - s) * (2 * Math.PI)) / p)) +
b
);
}
return (
a *
Math.pow(2, -10 * (t -= 1)) *
Math.sin(((t * d - s) * (2 * Math.PI)) / p) *
0.5 +
c +
b
);
},
backIn: function (t, b, c, d, s) {
if (typeof s == "undefined") {
s = 1.70158;
}
return c * (t /= d) * t * ((s + 1) * t - s) + b;
},
backOut: function (t, b, c, d, s) {
if (typeof s == "undefined") {
s = 1.70158;
}
return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
},
backBoth: function (t, b, c, d, s) {
if (typeof s == "undefined") {
s = 1.70158;
}
if ((t /= d / 2) < 1) {
return (c / 2) * (t * t * (((s *= 1.525) + 1) * t - s)) + b;
}
return (c / 2) * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b;
},
bounceIn: function (t, b, c, d) {
return c - Tween["bounceOut"](d - t, 0, c, d) + b;
},
bounceOut: function (t, b, c, d) {
//*
if ((t /= d) < 1 / 2.75) {
return c * (7.5625 * t * t) + b;
} else if (t < 2 / 2.75) {
return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b;
} else if (t < 2.5 / 2.75) {
return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b;
}
return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b;
},
bounceBoth: function (t, b, c, d) {
if (t < d / 2) {
return Tween["bounceIn"](t * 2, 0, c, d) * 0.5 + b;
}
return Tween["bounceOut"](t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
},
};
const transformAttrs = [
// 'matrix', //(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)
// 'matrix3d', //(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0)
// 'perspective', //(17px)
"rotate", //(0.5turn)
// 'rotate3d', //(1, 2.0, 3.0, 10deg)
"rotateX", //(10deg)
"rotateY", //(10deg)
"rotateZ", //(10deg)
"scale", //(2, 0.5)
// 'scale3d', //(2.5, 1.2, 0.3)
"scaleX", //(2)
"scaleY", //(0.5)
"scaleZ", //(0.3)
"skew", //(30deg, 20deg)
"skewX", //(30deg)
"skewY", //(1.07rad)
"translate", //(12px, 50%)
// 'translate3d', //(12px, 50%, 3em)
"translateX", //(2em)
"translateY", //(3in)
"translateZ", //(2px)
];
const numericalAttrs = [
"left",
"right",
"top",
"bottom",
"width",
"height",
"paddingTop",
"paddingBottom",
"paddingLeft",
"paddingRight",
"marginTop",
"marginBottom",
"marginLeft",
"marginRight",
];
function transformCSS(el, attr, value) {
el.transformData = el.transformData || {};
if (value === undefined) {
let result = el.transformData[attr];
if (result === undefined) {
if (["scale", "scaleX", "scaleY"].includes(attr)) {
result = 1;
} else {
result = 0;
}
}
return result;
}
let transformStr = "";
el.transformData[attr] = value;
for (let i in el.transformData) {
switch (i) {
case "translate":
case "translateX":
case "translateY":
case "translateZ":
transformStr += i + `(${el.transformData[i]}px) `;
break;
case "scale":
case "scaleX":
case "scaleY":
case "scaleZ":
transformStr += i + `(${el.transformData[i]}px) `;
break;
case "rotate":
case "rotatX":
case "rotatY":
case "rotatZ":
case "skew":
case "skewX":
case "skewY":
transformStr += i + `(${el.transformData[i]}deg) `;
break;
}
el.style.transform = transformStr.trim();
}
}
function css(el, attr, value) {
if (transformAttrs.includes(attr)) {
return transformCSS(el, attr, value);
}
if (value === undefined && typeof attr !== "object") {
let result = getComputedStyle(el)[attr];
if (numericalAttrs.includes(result) || !isNaN(parseFloat(result))) {
result = parseFloat(result);
}
return result;
} else {
if (typeof attr === "object") {
for (key in attr) {
css(el, key, attr[key]);
}
} else if (attr === "opacity") {
el.style.opacity = value;
} else if (numericalAttrs.includes(attr)) {
el.style[attr] = value + "px";
} else if (attr === "zIndex") {
el.style[attr] = parseInt(value);
} else {
el.style[attr] = value;
}
}
}
function mTween(opts) {
let { el, attr, duration = 500, fx = "easeOut" } = opts;
if (el.timer) return;
let maxC = 0;
if (typeof duration === "object") {
let durationOpt = duration;
duration.multiple = durationOpt.multiple || 2;
duration = maxC * duration.multiple;
duration = durationOpt.max ? Math.min(duration, durationOpt.max) : duration;
duration = durationOpt.min ? Math.max(duration, durationOpt.min) : duration;
}
let t = 0;
let b = {};
let c = {};
let d = Math.ceil(duration / (1000 / 60));
for (key in attr) {
b[key] = css(el, key);
c[key] = attr[key] - b[key];
maxC = Math.max(maxC, Math.abs(c[key]));
}
move();
function move() {
el.timer = requestAnimationFrame(() => {
t++;
if (t > d) {
el.timer = null;
opts.cb && opts.cb();
} else {
for (key in attr) {
let value = Tween[fx](t, b[key], c[key], d);
css(el, key, value);
}
move();
}
});
}
}
mTween.stop = function (el) {
cancelAnimationFrame(el.timer);
el.timer = null;
};
function shake(opts) {
let { el, attr, shakeLength = 15 } = opts;
let shakeArr = [];
el.shakeStart = {};
if (el.shake) {
return;
}
if (typeof attr === "object") {
for (let i = 0; i < attr.length; i++) {
el.shakeStart[attr[i]] = css(el, attr[i]);
}
} else {
el.shakeStart[attr] = css(el, attr);
}
for (let i = shakeLength; i >= 0; i--) {
shakeArr.push(i % 2 ? i : -i);
}
function move() {
el.shake = requestAnimationFrame(function () {
if (shakeArr.length <= 0) {
el.shake = false;
opts.cb && opts.cb();
} else {
let num = shakeArr.shift();
for (let s in el.shakeStart) {
css(el, s, el.shakeStart[s] + num);
}
move();
}
});
}
move();
}
shake.stop = function (el) {
cancelAnimationFrame(el.shake);
el.shake = false;
for (let s in el.shakeStart) {
css(el, s, el.shakeStart[s]);
}
};