I'm building a match 3 game for mobile with ionic/angular. For that i studied how touch gestures work.
I have a problem i'm trying to solve since a few days ago without success.
If i swipe move a tile to the left or to the right i can only move it across one tile.
If i move one tile up or down i can move it over as many tiles as i want.
I need to be able to move a tile left or right across as many tiles as i want.
Each tile has a with of 50px, therefore the sensitivity is equal to 50.
One strange thing i noticed and i'm not able to solve is that the onTouchEnd only fires if i swipe up or down. That maybe one of the causes of the problem because this.selectedTile = null does not happens and the touchmove does not have a new reference to swipe across path with more than 2 tiles.
I attached the relevant code and a picture of the game board.
Can somebody help me?
LOGIC (creating game board, touch gestures):
generateGameBoard(): void {
this.startTimer();
const rows = 6;
const cols = 6;
const board: Cell[][] = [];
// Calculate the total number of tiles needed for pairs of 3
const totalPairsOf3 = Math.floor((rows * cols) / 3);
let generatedPairsOf3 = 0;
// Shuffle the Egyptian hieroglyphs array to ensure randomness
const shuffledHieroglyphs = this.shuffleArray(this.symbols);
for (let i = 0; i < rows; i++) {
const row: Cell[] = [];
for (let j = 0; j < cols; j++) {
// Check if we have generated enough pairs of 3
if (generatedPairsOf3 < totalPairsOf3) {
// Generate pairs of 3
const pairIndex = generatedPairsOf3 % shuffledHieroglyphs.length;
const pairHieroglyph = shuffledHieroglyphs[pairIndex];
row.push({
symbol: pairHieroglyph?.symbol,
color: pairHieroglyph?.color,
});
generatedPairsOf3++;
} else {
// Fill the remaining tiles with random tiles
const randomIndex = Math.floor(Math.random() * this.symbols.length);
const randomHieroglyph = this.symbols[randomIndex];
row.push({
symbol: randomHieroglyph?.symbol,
color: randomHieroglyph?.color,
});
}
}
board.push(row);
}
this.gameBoard = board;
this.checkAdjacentTiles();
}
shuffleArray(array: any[]): any[] {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
onTouchStart(event: TouchEvent) {
event.preventDefault();
const touchedElement = event.touches[0].target as HTMLElement;
if (!touchedElement || !touchedElement.dataset) return;
const touchedRow = parseInt(touchedElement.dataset['row'] || '0', 10);
const touchedCol = parseInt(touchedElement.dataset['col'] || '0', 10);
console.log('Touched row:', touchedRow, 'Touched col:', touchedCol);
this.selectedTile = { row: touchedRow, col: touchedCol };
this.startX = event.touches[0].clientX;
this.startY = event.touches[0].clientY;
}
onTouchMove(event: TouchEvent) {
event.preventDefault();
if (!this.selectedTile) return;
const currentX = event.touches[0].clientX;
const currentY = event.touches[0].clientY;
const deltaX = currentX - this.startX;
const deltaY = currentY - this.startY;
const sensitivity = 50;
// Check if movement is primarily horizontal or vertical
const absDeltaX = Math.abs(deltaX);
const absDeltaY = Math.abs(deltaY);
const numTilesToMoveX = Math.floor(absDeltaX / 50);
const numTilesToMoveY = Math.floor(absDeltaY / 50);
let newRow = this.selectedTile.row;
let newCol = this.selectedTile.col;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal movement
if (deltaX > sensitivity) {
// Swipe right
console.log('Swiped right');
newRow += numTilesToMoveX;
} else if (deltaX < -sensitivity) {
// Swipe left
console.log('Swiped left');
newRow -= numTilesToMoveX;
}
} else {
// Vertical movement
if (deltaY > sensitivity) {
// Swipe down
console.log('Swiped down');
newCol += numTilesToMoveY;
} else if (deltaY < -sensitivity) {
// Swipe up
console.log('Swiped up');
newCol -= numTilesToMoveY;
}
}
newRow = Math.max(0, Math.min(newRow, this.gameBoard[0].length - 1));
newCol = Math.max(0, Math.min(newCol, this.gameBoard[0].length - 1));
if (newRow !== this.selectedTile.row || newCol !== this.selectedTile.col) {
// Swap the tiles
[
this.gameBoard[newRow][newCol],
this.gameBoard[this.selectedTile.row][this.selectedTile.col],
] = [
this.gameBoard[this.selectedTile.row][this.selectedTile.col],
this.gameBoard[newRow][newCol],
];
// Update the selected tile's position
this.selectedTile.row = newRow;
this.selectedTile.col = newCol;
// Update the start position for the next calculation
this.startX = currentX;
this.startY = currentY;
}
}
onTouchCancel(event: TouchEvent) {
this.onTouchEnd(event);
}
onTouchEnd(event: TouchEvent) {
console.log('touchend');
this.soundService.playSfx();
this.checkAdjacentTiles();
event.preventDefault();
this.selectedTile = null;
}
MARKUP:
<div
class="game-board"
(touchstart)="onTouchStart($event)"
(touchmove)="onTouchMove($event)"
(touchcancel)="onTouchCancel($event)"
(touchend)="onTouchEnd($event)"
>
<div class="row" *ngFor="let row of gameBoard; let i = index">
<div
class="cell"
*ngFor="let cell of row; let j = index"
[attr.data-row]="i"
[attr.data-col]="j"
[style.color]="cell.color"
[class.glow]="cell.glow"
>
{{ cell.symbol }}
</div>
</div>
</div>
