KEMBAR78
Match24 Game Code | PDF
0% found this document useful (0 votes)
47 views16 pages

Match24 Game Code

The document describes a web-based math puzzle game called 'Make 24', where players drag numbers and operators to form equations that equal 24. It includes a start screen, game mechanics for dragging and dropping elements, and event listeners for user interactions. The game features levels with predefined number sets and checks for correct equations to advance the player through levels.

Uploaded by

dantrung18091995
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
47 views16 pages

Match24 Game Code

The document describes a web-based math puzzle game called 'Make 24', where players drag numbers and operators to form equations that equal 24. It includes a start screen, game mechanics for dragging and dropping elements, and event listeners for user interactions. The game features levels with predefined number sets and checks for correct equations to advance the player through levels.

Uploaded by

dantrung18091995
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 16

<!

DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Make 24 - Math Puzzle Game</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
}

body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
height: 100vh;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}

#gameContainer {
width: 100vw;
height: 100vh;
max-width: 400px;
max-height: 711px;
position: relative;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}

#gameCanvas {
width: 100%;
height: 100%;
display: block;
touch-action: none;
}

.particle {
position: absolute;
pointer-events: none;
border-radius: 50%;
animation: particle-burst 1s ease-out forwards;
}

@keyframes particle-burst {
0% {
transform: scale(1) translate(0, 0);
opacity: 1;
}
100% {
transform: scale(0) translate(var(--dx), var(--dy));
opacity: 0;
}
}

@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}

.shake {
animation: shake 0.5s ease-in-out;
}

@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}

.pulse {
animation: pulse 0.3s ease-in-out;
}

#startScreen, #gameOverScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
z-index: 1000;
}

.screen-title {
font-size: 3em;
font-weight: bold;
margin-bottom: 20px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}

.screen-subtitle {
font-size: 1.2em;
margin-bottom: 30px;
text-align: center;
padding: 0 20px;
}

.btn {
background: linear-gradient(45deg, #ff6b6b, #ff8e8e);
color: white;
border: none;
padding: 15px 30px;
font-size: 1.2em;
border-radius: 25px;
cursor: pointer;
margin: 10px;
min-width: 120px;
min-height: 50px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}

.btn:hover, .btn:active {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
}

.hidden {
display: none !important;
}
</style>
</head>
<body>
<div id="gameContainer">
<div id="startScreen">
<div class="screen-title">Make 24</div>
<div class="screen-subtitle">Drag numbers and operators to create an
equation that equals 24!</div>
<button class="btn" onclick="startGame()">Start Game</button>
</div>

<div id="gameOverScreen" class="hidden">


<div class="screen-title">Level Complete!</div>
<div class="screen-subtitle" id="gameOverText"></div>
<button class="btn" onclick="nextLevel()">Next Level</button>
<button class="btn" onclick="restartGame()">Restart</button>
</div>

<canvas id="gameCanvas"></canvas>
</div>

<script>
class Make24Game {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.setupCanvas();

this.gameState = 'start'; // start, playing, gameover


this.level = 1;
this.score = 0;
this.timer = 0;
this.startTime = 0;

this.draggedItem = null;
this.dragOffset = { x: 0, y: 0 };
this.isTouch = false;

this.numbers = [];
this.operators = ['+', '-', '×', '÷'];
this.equationSlots = [];
this.selectedItems = [];
this.previewResult = null;

this.setupLevel();
this.setupEventListeners();
this.gameLoop();
}

setupCanvas() {
const container = document.getElementById('gameContainer');
const rect = container.getBoundingClientRect();

// Maintain 9:16 aspect ratio


this.canvas.width = rect.width * window.devicePixelRatio;
this.canvas.height = rect.height * window.devicePixelRatio;
this.canvas.style.width = rect.width + 'px';
this.canvas.style.height = rect.height + 'px';

this.ctx.scale(window.devicePixelRatio, window.devicePixelRatio);

this.width = rect.width;
this.height = rect.height;
}

setupLevel() {
// Generate random numbers that can make 24
const solutions = [
[1, 1, 8, 3], [1, 2, 3, 4], [1, 3, 4, 6], [1, 4, 5, 6],
[2, 2, 6, 2], [2, 3, 4, 4], [2, 4, 4, 3], [3, 3, 8, 1],
[4, 4, 2, 3], [6, 6, 1, 3], [8, 8, 1, 2], [9, 3, 2, 4]
];

const currentSolution = solutions[(this.level - 1) %


solutions.length];
this.targetNumbers = [...currentSolution];

// Create draggable number objects


this.numbers = [];
for (let i = 0; i < 4; i++) {
this.numbers.push({
value: this.targetNumbers[i],
x: 60 + (i * 80),
y: 150,
originalX: 60 + (i * 80),
originalY: 150,
width: 60,
height: 60,
isDragging: false,
type: 'number',
inSlot: false,
slotIndex: -1
});
}

// Create operator objects


this.operatorObjects = [];
for (let i = 0; i < 4; i++) {
this.operatorObjects.push({
value: this.operators[i],
x: 60 + (i * 80),
y: 250,
originalX: 60 + (i * 80),
originalY: 250,
width: 60,
height: 60,
isDragging: false,
type: 'operator',
inSlot: false,
slotIndex: -1
});
}

// Create equation slots (make them smaller and more compact)


this.equationSlots = [];
const slotSize = 40; // Reduced from 44
const startX = 30;
const spacing = 45; // Reduced spacing

for (let i = 0; i < 7; i++) {


this.equationSlots.push({
x: startX + (i * spacing),
y: this.height - 110,
width: slotSize,
height: slotSize,
occupied: false,
item: null,
type: i % 2 === 0 ? 'number' : 'operator'
});
}

this.selectedItems = [];
this.previewResult = null;
this.startTime = Date.now();
}

setupEventListeners() {
// Mouse events
this.canvas.addEventListener('mousedown', (e) =>
this.handleStart(e, false));
this.canvas.addEventListener('mousemove', (e) => this.handleMove(e,
false));
this.canvas.addEventListener('mouseup', (e) => this.handleEnd(e,
false));

// Touch events
this.canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
this.handleStart(e.touches[0], true);
});
this.canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
this.handleMove(e.touches[0], true);
});
this.canvas.addEventListener('touchend', (e) => {
e.preventDefault();
this.handleEnd(e.changedTouches[0], true);
});
}
getEventPos(e) {
const rect = this.canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}

handleStart(e, isTouch) {
if (this.gameState !== 'playing') return;

const pos = this.getEventPos(e);


this.isTouch = isTouch;

// Check numbers
for (let item of this.numbers) {
if (this.isPointInRect(pos, item) && !item.isDragging) {
if (this.isTouch) {
this.handleSelection(item);
} else {
this.startDrag(item, pos);
}
return;
}
}

// Check operators
for (let item of this.operatorObjects) {
if (this.isPointInRect(pos, item) && !item.isDragging) {
if (this.isTouch) {
this.handleSelection(item);
} else {
this.startDrag(item, pos);
}
return;
}
}
}

handleSelection(item) {
// Add visual selection feedback
item.selected = !item.selected;

// Clear other selections of same type


const allItems = [...this.numbers, ...this.operatorObjects];
allItems.forEach(otherItem => {
if (otherItem !== item && otherItem.type === item.type) {
otherItem.selected = false;
}
});

// Update selected items array


this.selectedItems = this.selectedItems.filter(selectedItem =>
selectedItem.selected && selectedItem !== item);

if (item.selected) {
this.selectedItems.push(item);
}
// Check if we have number -> operator -> number pattern
this.checkCalculationPreview();

// Haptic feedback
if (navigator.vibrate) {
navigator.vibrate(10);
}
}

checkCalculationPreview() {
// Clear previous preview
this.previewResult = null;

// Need exactly 3 items: number, operator, number


if (this.selectedItems.length !== 3) return;

const [first, second, third] = this.selectedItems;

// Check pattern: number -> operator -> number


if (first.type === 'number' && second.type === 'operator' &&
third.type === 'number') {
try {
const result = this.calculatePreview(first.value,
second.value, third.value);
this.previewResult = {
value: result,
x: third.x + third.width + 10,
y: third.y,
targetItem: third
};
} catch (e) {
// Invalid calculation
}
}
}

calculatePreview(num1, operator, num2) {


switch (operator) {
case '+': return num1 + num2;
case '-': return num1 - num2;
case '×': return num1 * num2;
case '÷':
if (num2 === 0) throw new Error('Division by zero');
return num1 / num2;
default: throw new Error('Invalid operator');
}
}

startDrag(item, pos) {
this.draggedItem = item;
item.isDragging = true;
this.dragOffset.x = pos.x - item.x;
this.dragOffset.y = pos.y - item.y;

// Remove from slot if it was in one


if (item.inSlot) {
this.equationSlots[item.slotIndex].occupied = false;
this.equationSlots[item.slotIndex].item = null;
item.inSlot = false;
item.slotIndex = -1;
}

// Haptic feedback
if (navigator.vibrate) {
navigator.vibrate(10);
}
}

handleMove(e, isTouch) {
if (!this.draggedItem || this.gameState !== 'playing' ||
this.isTouch) return;

const pos = this.getEventPos(e);


this.draggedItem.x = pos.x - this.dragOffset.x;
this.draggedItem.y = pos.y - this.dragOffset.y;
}

handleEnd(e, isTouch) {
if (!this.draggedItem || this.gameState !== 'playing' ||
this.isTouch) return;

const pos = this.getEventPos(e);


const item = this.draggedItem;

// Check if dropped on equation slot


let droppedOnSlot = false;
for (let i = 0; i < this.equationSlots.length; i++) {
const slot = this.equationSlots[i];
if (this.isPointInRect(pos, slot) && !slot.occupied &&
((slot.type === 'number' && item.type === 'number') ||
(slot.type === 'operator' && item.type === 'operator'))) {

// Snap to slot (adjust size to fit)


item.x = slot.x;
item.y = slot.y;
item.width = slot.width;
item.height = slot.height;
item.inSlot = true;
item.slotIndex = i;
slot.occupied = true;
slot.item = item;
droppedOnSlot = true;

// Haptic feedback
if (navigator.vibrate) {
navigator.vibrate(20);
}

break;
}
}

if (!droppedOnSlot) {
// Return to original position and size
item.x = item.originalX;
item.y = item.originalY;
item.width = 60;
item.height = 60;
}

item.isDragging = false;
this.draggedItem = null;

// Check if equation is complete


this.checkEquation();
}

isPointInRect(point, rect) {
return point.x >= rect.x && point.x <= rect.x + rect.width &&
point.y >= rect.y && point.y <= rect.y + rect.height;
}

checkEquation() {
// Check if all slots are filled
let allFilled = true;
for (let slot of this.equationSlots) {
if (!slot.occupied) {
allFilled = false;
break;
}
}

if (!allFilled) return;

// Build equation string


let equation = '';
for (let slot of this.equationSlots) {
if (slot.item.type === 'number') {
equation += slot.item.value;
} else {
equation += slot.item.value === '×' ? '*' :
slot.item.value === '÷' ? '/' : slot.item.value;
}
}

try {
const result = this.evaluateExpression(equation);
if (Math.abs(result - 24) < 0.0001) {
this.levelComplete();
} else {
this.showError();
}
} catch (e) {
this.showError();
}
}

evaluateExpression(expr) {
// Simple expression evaluator with proper order of operations
const tokens = expr.match(/\d+|\+|\-|\*|\//g);
const numbers = [];
const operators = [];

for (let token of tokens) {


if (!isNaN(token)) {
numbers.push(parseFloat(token));
} else {
operators.push(token);
}
}

// Handle multiplication and division first


for (let i = 0; i < operators.length; i++) {
if (operators[i] === '*' || operators[i] === '/') {
const result = operators[i] === '*' ?
numbers[i] * numbers[i + 1] :
numbers[i] / numbers[i + 1];

numbers.splice(i, 2, result);
operators.splice(i, 1);
i--;
}
}

// Handle addition and subtraction


let result = numbers[0];
for (let i = 0; i < operators.length; i++) {
if (operators[i] === '+') {
result += numbers[i + 1];
} else if (operators[i] === '-') {
result -= numbers[i + 1];
}
}

return result;
}

levelComplete() {
this.timer = (Date.now() - this.startTime) / 1000;
this.score += Math.max(1000 - Math.floor(this.timer * 10), 100);

// Particle effects
this.createParticleExplosion(this.width / 2, this.height / 2);

// Haptic feedback
if (navigator.vibrate) {
navigator.vibrate([100, 50, 100]);
}

setTimeout(() => {
this.gameState = 'gameover';
document.getElementById('gameOverText').textContent =
`Time: ${this.timer.toFixed(1)}s | Score: ${this.score}`;

document.getElementById('gameOverScreen').classList.remove('hidden');
}, 1000);
}

showError() {
// Shake animation and error feedback
const canvas = document.getElementById('gameCanvas');
canvas.classList.add('shake');
setTimeout(() => canvas.classList.remove('shake'), 500);

// Clear equation slots


for (let slot of this.equationSlots) {
if (slot.occupied && slot.item) {
slot.item.x = slot.item.originalX;
slot.item.y = slot.item.originalY;
slot.item.width = 60; // Reset to original size
slot.item.height = 60;
slot.item.inSlot = false;
slot.item.slotIndex = -1;
slot.occupied = false;
slot.item = null;
}
}

// Haptic feedback
if (navigator.vibrate) {
navigator.vibrate([50, 50, 50]);
}
}

createParticleExplosion(x, y) {
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4',
'#feca57'];
const container = document.getElementById('gameContainer');

for (let i = 0; i < 20; i++) {


const particle = document.createElement('div');
particle.className = 'particle';
particle.style.left = x + 'px';
particle.style.top = y + 'px';
particle.style.backgroundColor =
colors[Math.floor(Math.random() * colors.length)];
particle.style.width = Math.random() * 8 + 4 + 'px';
particle.style.height = particle.style.width;

const dx = (Math.random() - 0.5) * 200;


const dy = (Math.random() - 0.5) * 200;
particle.style.setProperty('--dx', dx + 'px');
particle.style.setProperty('--dy', dy + 'px');

container.appendChild(particle);

setTimeout(() => {
container.removeChild(particle);
}, 1000);
}
}

draw() {
// Clear canvas
this.ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
this.ctx.fillRect(0, 0, this.width, this.height);

if (this.gameState !== 'playing') return;

// Draw header
this.drawHeader();
// Draw numbers
for (let number of this.numbers) {
this.drawItem(number, '#4ecdc4');
}

// Draw operators
for (let op of this.operatorObjects) {
this.drawItem(op, '#ff6b6b');
}

// Draw equation slots


this.drawEquationArea();

// Draw calculation preview


this.drawCalculationPreview();
}

drawHeader() {
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 24px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText(`Level ${this.level}`, this.width / 2, 40);

this.ctx.font = '18px Arial';


this.ctx.fillText(`Score: ${this.score}`, this.width / 4, 70);

const currentTime = (Date.now() - this.startTime) / 1000;


this.ctx.fillText(`Time: ${currentTime.toFixed(1)}s`, (this.width *
3) / 4, 70);

// Target display
this.ctx.font = 'bold 32px Arial';
this.ctx.fillStyle = '#feca57';
this.ctx.fillText('Make 24!', this.width / 2, 110);
}

drawItem(item, color) {
const x = item.x;
const y = item.y;
const size = item.width;

// Selection highlight
if (item.selected) {
this.ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
this.ctx.beginPath();
if (item.type === 'number') {
this.ctx.arc(x + size/2, y + size/2, size/2 + 4, 0, Math.PI
* 2);
} else {
this.ctx.roundRect(x - 4, y - 4, size + 8, size + 8, 12);
}
this.ctx.fill();
}

// Shadow
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
this.ctx.beginPath();
if (item.type === 'number') {
this.ctx.arc(x + size/2 + 2, y + size/2 + 2, size/2, 0, Math.PI
* 2);
} else {
this.ctx.roundRect(x + 2, y + 2, size, size, 8);
}
this.ctx.fill();

// Main shape
this.ctx.fillStyle = item.isDragging ?
this.lightenColor(color, 20) : color;
this.ctx.beginPath();
if (item.type === 'number') {
this.ctx.arc(x + size/2, y + size/2, size/2, 0, Math.PI * 2);
} else {
this.ctx.roundRect(x, y, size, size, 8);
}
this.ctx.fill();

// Border
this.ctx.strokeStyle = item.selected ?
'rgba(255, 255, 255, 0.8)' : 'rgba(255, 255, 255, 0.3)';
this.ctx.lineWidth = item.selected ? 3 : 2;
this.ctx.stroke();

// Text
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 24px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText(item.value, x + size/2, y + size/2 + 8);
}

drawEquationArea() {
// Background
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
this.ctx.roundRect(10, this.height - 130, this.width - 20, 70, 10);
this.ctx.fill();

// Equation label
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 14px Arial';
this.ctx.textAlign = 'left';
this.ctx.fillText('Build equation:', 20, this.height - 135);

// Draw slots
for (let i = 0; i < this.equationSlots.length; i++) {
const slot = this.equationSlots[i];

this.ctx.strokeStyle = slot.occupied ?
'rgba(255, 255, 255, 0.8)' : 'rgba(255, 255, 255, 0.3)';
this.ctx.lineWidth = 2;
this.ctx.setLineDash(slot.occupied ? [] : [4, 4]);

if (slot.type === 'number') {


this.ctx.beginPath();
this.ctx.arc(slot.x + slot.width/2, slot.y + slot.height/2,
slot.width/2, 0, Math.PI * 2);
this.ctx.stroke();
} else {
this.ctx.roundRect(slot.x, slot.y, slot.width, slot.height,
6);
this.ctx.stroke();
}
}

// Draw equals sign and 24 (positioned to fit better)


const lastSlot = this.equationSlots[this.equationSlots.length - 1];
const equalsX = lastSlot.x + lastSlot.width + 15;

this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 18px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText('=', equalsX, lastSlot.y + lastSlot.height/2 +
6);

// Draw 24 in a circle
const twentyFourX = equalsX + 25;
this.ctx.fillStyle = '#feca57';
this.ctx.beginPath();
this.ctx.arc(twentyFourX, lastSlot.y + lastSlot.height/2, 18, 0,
Math.PI * 2);
this.ctx.fill();

this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';


this.ctx.lineWidth = 2;
this.ctx.stroke();

this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 16px Arial';
this.ctx.fillText('24', twentyFourX, lastSlot.y + lastSlot.height/2
+ 5);

this.ctx.setLineDash([]);
}

drawCalculationPreview() {
if (!this.previewResult) return;

const preview = this.previewResult;


const radius = 25;

// Preview circle background


this.ctx.fillStyle = 'rgba(252, 202, 87, 0.9)';
this.ctx.beginPath();
this.ctx.arc(preview.x + radius, preview.y + radius, radius, 0,
Math.PI * 2);
this.ctx.fill();

// Border
this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
this.ctx.lineWidth = 2;
this.ctx.stroke();

// Result text
this.ctx.fillStyle = 'white';
this.ctx.font = 'bold 16px Arial';
this.ctx.textAlign = 'center';
const displayValue = Number.isInteger(preview.value) ?
preview.value.toString() : preview.value.toFixed(1);
this.ctx.fillText(displayValue, preview.x + radius, preview.y +
radius + 5);

// Connection line
this.ctx.strokeStyle = 'rgba(252, 202, 87, 0.6)';
this.ctx.lineWidth = 2;
this.ctx.setLineDash([5, 5]);
this.ctx.beginPath();
this.ctx.moveTo(preview.targetItem.x + preview.targetItem.width,
preview.targetItem.y + preview.targetItem.height/2);
this.ctx.lineTo(preview.x, preview.y + radius);
this.ctx.stroke();
this.ctx.setLineDash([]);
}

lightenColor(color, percent) {
const num = parseInt(color.replace("#",""), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) + amt;
const G = (num >> 8 & 0x00FF) + amt;
const B = (num & 0x0000FF) + amt;
return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000
+
(G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
(B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1);
}

gameLoop() {
this.draw();
requestAnimationFrame(() => this.gameLoop());
}
}

// Polyfill for roundRect


if (!CanvasRenderingContext2D.prototype.roundRect) {
CanvasRenderingContext2D.prototype.roundRect = function(x, y, width,
height, radius) {
this.beginPath();
this.moveTo(x + radius, y);
this.lineTo(x + width - radius, y);
this.quadraticCurveTo(x + width, y, x + width, y + radius);
this.lineTo(x + width, y + height - radius);
this.quadraticCurveTo(x + width, y + height, x + width - radius, y
+ height);
this.lineTo(x + radius, y + height);
this.quadraticCurveTo(x, y + height, x, y + height - radius);
this.lineTo(x, y + radius);
this.quadraticCurveTo(x, y, x + radius, y);
};
}

let game;

function startGame() {
document.getElementById('startScreen').classList.add('hidden');
game = new Make24Game();
game.gameState = 'playing';
}

function nextLevel() {
document.getElementById('gameOverScreen').classList.add('hidden');
game.level++;
game.setupLevel();
game.gameState = 'playing';
}

function restartGame() {
document.getElementById('gameOverScreen').classList.add('hidden');
game.level = 1;
game.score = 0;
game.setupLevel();
game.gameState = 'playing';
}

// Handle window resize


window.addEventListener('resize', () => {
if (game) {
game.setupCanvas();
}
});
</script>
</body>
</html>

You might also like