
An visual IQ test generator
Prompt
Create an IQ test generator that runs entirely in the browser, generates simple graphical 3×3 sequence-completion tasks whose images are derived from mathematical transformation functions to ensure logical consistency, supports three difficulty levels, uses multiple-choice selection for the missing ninth image, tracks user responses, and outputs an approximate IQ estimate.
A system prompt was added to support web rendering
An IQ test generator that runs entirely in the browser, generates simple graphical 3×3 sequence-completion tasks whose images are derived from mathematical transformation functions to ensure logical consistency, supports three difficulty levels, uses multiple-choice selection for the missing ninth image, tracks user responses, and outputs an approximate IQ estimate.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IQ Test Generator</title>
<style>
:root {
--bg-color: #f0f2f5;
--container-bg: #ffffff;
--primary-text: #333;
--secondary-text: #555;
--border-color: #dbe2e8;
--accent-color: #007bff;
--accent-hover: #0056b3;
--correct-color: #28a745;
--incorrect-color: #dc3545;
--shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: var(--bg-color);
color: var(--primary-text);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
box-sizing: border-box;
}
.app-container {
width: 100%;
max-width: 600px;
background-color: var(--container-bg);
border-radius: 12px;
box-shadow: var(--shadow);
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
}
h1 {
color: var(--primary-text);
margin-bottom: 0.5rem;
}
h2 {
color: var(--primary-text);
margin-top: 0;
}
p {
color: var(--secondary-text);
line-height: 1.6;
margin-bottom: 1.5rem;
}
.screen {
display: none;
}
.screen.active {
display: block;
}
.difficulty-selection button {
background-color: var(--accent-color);
color: white;
border: none;
padding: 12px 24px;
margin: 5px;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: background-color 0.2s ease, transform 0.2s ease;
}
.difficulty-selection button:hover {
background-color: var(--accent-hover);
transform: translateY(-2px);
}
#question-status {
margin-bottom: 1rem;
font-weight: 500;
color: var(--secondary-text);
}
.puzzle-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
width: 330px;
margin: 1.5rem auto;
border: 2px solid var(--border-color);
padding: 10px;
border-radius: 8px;
}
.grid-cell {
width: 100px;
height: 100px;
background-color: #f8f9fa;
border: 1px solid var(--border-color);
display: flex;
justify-content: center;
align-items: center;
border-radius: 4px;
}
.grid-cell.question-mark {
font-size: 3rem;
color: var(--accent-color);
font-weight: bold;
}
.options-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
grid-gap: 15px;
margin-top: 2rem;
}
.option-cell {
height: 100px;
border: 2px solid var(--border-color);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
background-color: #fff;
}
.option-cell:hover {
border-color: var(--accent-color);
transform: scale(1.05);
}
.result-score {
font-size: 1.8rem;
font-weight: bold;
color: var(--accent-color);
margin: 1rem 0;
}
.result-iq {
font-size: 2.5rem;
font-weight: bold;
color: var(--primary-text);
margin-bottom: 2rem;
}
canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="app-container">
<!-- Start Screen -->
<div id="start-screen" class="screen active">
<h1>Graphical IQ Test</h1>
<p>Complete the 3x3 pattern by choosing the correct image for the final square. Select a difficulty level to begin.</p>
<div class="difficulty-selection">
<button onclick="startGame('easy')">Easy</button>
<button onclick="startGame('medium')">Medium</button>
<button onclick="startGame('hard')">Hard</button>
</div>
</div>
<!-- Test Screen -->
<div id="test-screen" class="screen">
<h2 id="question-status">Question 1 of 10</h2>
<div class="puzzle-grid" id="puzzle-grid">
<!-- Grid cells will be generated by JS -->
</div>
<p>Which option completes the pattern?</p>
<div class="options-grid" id="options-grid">
<!-- Options will be generated by JS -->
</div>
</div>
<!-- Results Screen -->
<div id="results-screen" class="screen">
<h1>Test Complete!</h1>
<p>You answered:</p>
<div class="result-score" id="final-score"></div>
<p>Based on your performance, your estimated IQ is:</p>
<div class="result-iq" id="iq-estimate"></div>
<button onclick="restartTest()">Try Again</button>
</div>
</div>
<script>
const DOM = {
screens: document.querySelectorAll('.screen'),
startScreen: document.getElementById('start-screen'),
testScreen: document.getElementById('test-screen'),
resultsScreen: document.getElementById('results-screen'),
questionStatus: document.getElementById('question-status'),
puzzleGrid: document.getElementById('puzzle-grid'),
optionsGrid: document.getElementById('options-grid'),
finalScore: document.getElementById('final-score'),
iqEstimate: document.getElementById('iq-estimate'),
};
let gameState = {};
const CONFIG = {
SHAPES: ['square', 'circle', 'triangle', 'cross', 'star'],
COLORS: ['#3498db', '#e74c3c', '#2ecc71', '#f1c40f', '#9b59b6'],
FILL_STYLES: [true, false],
QUESTIONS_PER_DIFFICULTY: {
easy: 8,
medium: 10,
hard: 12
},
OPTIONS_COUNT: 6
};
// --- UTILITY FUNCTIONS ---
const util = {
shuffle: (array) => array.sort(() => Math.random() - 0.5),
getRandom: (arr) => arr[Math.floor(Math.random() * arr.length)],
clone: (obj) => JSON.parse(JSON.stringify(obj)),
getNextInCycle: (arr, current) => {
const currentIndex = arr.indexOf(current);
return arr[(currentIndex + 1) % arr.length];
}
};
// --- DRAWING FUNCTIONS ---
const drawer = {
draw: (ctx, state) => {
if (!state) return;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
const scale = state.size || 0.6;
ctx.scale(scale, scale);
ctx.rotate((state.rotation || 0) * Math.PI / 180);
ctx.strokeStyle = state.color || '#000000';
ctx.fillStyle = state.color || '#000000';
ctx.lineWidth = 10;
drawer[state.shape](ctx);
if (state.fill) {
ctx.fill();
} else {
ctx.stroke();
}
ctx.restore();
},
square: (ctx) => {
ctx.beginPath();
ctx.rect(-50, -50, 100, 100);
},
circle: (ctx) => {
ctx.beginPath();
ctx.arc(0, 0, 50, 0, 2 * Math.PI);
},
triangle: (ctx) => {
ctx.beginPath();
ctx.moveTo(0, -58);
ctx.lineTo(50, 50);
ctx.lineTo(-50, 50);
ctx.closePath();
},
cross: (ctx) => {
ctx.beginPath();
ctx.moveTo(-50, -15); ctx.lineTo(-15, -15);
ctx.lineTo(-15, -50); ctx.lineTo(15, -50);
ctx.lineTo(15, -15); ctx.lineTo(50, -15);
ctx.lineTo(50, 15); ctx.lineTo(15, 15);
ctx.lineTo(15, 50); ctx.lineTo(-15, 50);
ctx.lineTo(-15, 15); ctx.lineTo(-50, 15);
ctx.closePath();
},
star: (ctx) => {
ctx.beginPath();
ctx.moveTo(0, -50);
for (let i = 0; i < 5; i++) {
ctx.rotate(Math.PI / 5);
ctx.lineTo(0, -(50 * 0.4));
ctx.rotate(Math.PI / 5);
ctx.lineTo(0, -50);
}
ctx.closePath();
}
};
// --- TRANSFORMATION LOGIC ---
const transforms = {
identity: (s) => util.clone(s),
rotate45: (s) => (s.rotation = ((s.rotation || 0) + 45) % 360, s),
rotate90: (s) => (s.rotation = ((s.rotation || 0) + 90) % 360, s),
nextShape: (s) => (s.shape = util.getNextInCycle(CONFIG.SHAPES, s.shape), s),
nextColor: (s) => (s.color = util.getNextInCycle(CONFIG.COLORS, s.color), s),
toggleFill: (s) => (s.fill = !s.fill, s)
};
// --- LOGICAL OPERATIONS for row 3 ---
const operations = {
// Row3 is combination of Row1 and Row2 properties
superposition: (s1, s2) => {
let newState = util.clone(s1);
// Simple version: s2 color becomes s1's fill or vice versa
if(s1.fill && !s2.fill) newState.fill = false;
else if(!s1.fill && s2.fill) newState.fill = true;
newState.rotation = (s1.rotation + s2.rotation) % 360;
return newState;
},
// Row3 shape is XOR of Row1 and Row2
xorShape: (s1, s2) => {
let newState = util.clone(s1);
if(s1.shape === s2.shape) {
newState.shape = util.getNextInCycle(CONFIG.SHAPES, s1.shape);
}
return newState;
}
}
// --- QUESTION GENERATION ---
function generateQuestion(difficulty) {
const type = (difficulty === 'hard' && Math.random() > 0.5) ? 'operation' : 'progression';
if (type === 'progression') {
return generateProgressionQuestion(difficulty);
} else {
return generateOperationQuestion(difficulty);
}
}
function generateProgressionQuestion(difficulty) {
let baseState = {
shape: util.getRandom(CONFIG.SHAPES),
color: util.getRandom(CONFIG.COLORS),
rotation: util.getRandom([0, 45, 90]),
fill: util.getRandom(CONFIG.FILL_STYLES),
};
let rowTransforms = [transforms.identity];
let colTransforms = [transforms.identity];
const availableTransforms = [transforms.rotate45, transforms.rotate90, transforms.nextColor, transforms.nextShape, transforms.toggleFill];
rowTransforms = [util.getRandom(availableTransforms)];
if(difficulty === 'easy' && Math.random() > 0.5) { // 50% chance of only one transform
colTransforms = [transforms.identity];
} else {
colTransforms = [util.getRandom(availableTransforms.filter(t => t !== rowTransforms[0]))];
}
if (difficulty === 'medium') {
if (Math.random() > 0.5) rowTransforms.push(util.getRandom(availableTransforms.filter(t => !rowTransforms.includes(t))));
} else if (difficulty === 'hard') {
rowTransforms.push(util.getRandom(availableTransforms.filter(t => !rowTransforms.includes(t))));
colTransforms.push(util.getRandom(availableTransforms.filter(t => !colTransforms.includes(t))));
}
const grid = Array(9).fill(null);
for (let r = 0; r < 3; r++) {
for (let c = 0; c < 3; c++) {
let state = util.clone(baseState);
for (let i = 0; i < c; i++) rowTransforms.forEach(t => state = t(state));
for (let i = 0; i < r; i++) colTransforms.forEach(t => state = t(state));
grid[r * 3 + c] = state;
}
}
const correctAnswer = grid[8];
const distractors = generateDistractors(correctAnswer, {row: rowTransforms, col: colTransforms, base: baseState});
const options = util.shuffle([correctAnswer, ...distractors]);
return { grid, options, correctAnswer };
}
function generateOperationQuestion(difficulty) {
// Rule: Grid[2][c] = Op(Grid[0][c], Grid[1][c])
const op = util.getRandom(Object.values(operations));
const grid = Array(9).fill(null);
// Generate first two rows with simple progression
const row0Base = { shape: util.getRandom(CONFIG.SHAPES), color: util.getRandom(CONFIG.COLORS), rotation: 0, fill: true };
const row1Base = { shape: util.getRandom(CONFIG.SHAPES), color: util.getRandom(CONFIG.COLORS), rotation: 0, fill: true };
const rowProgression = util.getRandom([transforms.nextShape, transforms.nextColor, transforms.rotate90]);
for(let c = 0; c < 3; c++) {
let r0State = util.clone(row0Base);
let r1State = util.clone(row1Base);
for(let i = 0; i<c; i++) {
r0State = rowProgression(r0State);
r1State = rowProgression(r1State); // can use same or different progression
}
grid[c] = r0State;
grid[3+c] = r1State;
grid[6+c] = op(r0State, r1State);
}
const correctAnswer = grid[8];
const distractors = generateDistractors(correctAnswer, {});
const options = util.shuffle([correctAnswer, ...distractors]);
return { grid, options, correctAnswer };
}
function generateDistractors(correctAnswer, rules) {
const distractors = new Set();
while(distractors.size < CONFIG.OPTIONS_COUNT - 1) {
let distractor = util.clone(correctAnswer);
// Plausible distractors
if(rules.row && Math.random() > 0.5) { // Apply row transform again
rules.row.forEach(t => distractor = t(distractor));
} else if(rules.col && Math.random() > 0.5) { // Apply col transform again
rules.col.forEach(t => distractor = t(distractor));
} else { // Random mutation
const propToChange = util.getRandom(['shape', 'color', 'rotation', 'fill']);
switch (propToChange) {
case 'shape': distractor.shape = util.getRandom(CONFIG.SHAPES); break;
case 'color': distractor.color = util.getRandom(CONFIG.COLORS); break;
case 'rotation': distractor.rotation = (distractor.rotation + util.getRandom([45, 90])) % 360; break;
case 'fill': distractor.fill = !distractor.fill; break;
}
}
// ensure no duplicates
distractors.add(JSON.stringify(distractor));
}
return Array.from(distractors).map(s => JSON.parse(s));
}
// --- GAME FLOW ---
function changeScreen(activeScreen) {
DOM.screens.forEach(s => s.classList.remove('active'));
activeScreen.classList.add('active');
}
function startGame(difficulty) {
gameState = {
difficulty,
score: 0,
currentQuestion: 0,
totalQuestions: CONFIG.QUESTIONS_PER_DIFFICULTY[difficulty],
questions: []
};
for (let i = 0; i < gameState.totalQuestions; i++) {
gameState.questions.push(generateQuestion(difficulty));
}
changeScreen(DOM.testScreen);
displayQuestion();
}
function displayQuestion() {
const q = gameState.questions[gameState.currentQuestion];
DOM.questionStatus.textContent = `Question ${gameState.currentQuestion + 1} of ${gameState.totalQuestions}`;
// Populate puzzle grid
DOM.puzzleGrid.innerHTML = '';
for (let i = 0; i < 9; i++) {
const cell = document.createElement('div');
cell.className = 'grid-cell';
if (i === 8) {
cell.classList.add('question-mark');
cell.textContent = '?';
} else {
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
cell.appendChild(canvas);
const ctx = canvas.getContext('2d');
drawer.draw(ctx, q.grid[i]);
}
DOM.puzzleGrid.appendChild(cell);
}
// Populate options grid
DOM.optionsGrid.innerHTML = '';
q.options.forEach((optionState, index) => {
const cell = document.createElement('div');
cell.className = 'option-cell';
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
cell.appendChild(canvas);
const ctx = canvas.getContext('2d');
drawer.draw(ctx, optionState);
cell.onclick = () => selectAnswer(optionState);
DOM.optionsGrid.appendChild(cell);
});
}
function selectAnswer(selectedOption) {
const q = gameState.questions[gameState.currentQuestion];
// Deep comparison of objects
if (JSON.stringify(selectedOption) === JSON.stringify(q.correctAnswer)) {
gameState.score++;
}
gameState.currentQuestion++;
if (gameState.currentQuestion < gameState.totalQuestions) {
displayQuestion();
} else {
showResults();
}
}
function showResults() {
changeScreen(DOM.resultsScreen);
const score = gameState.score;
const total = gameState.totalQuestions;
DOM.finalScore.textContent = `${score} / ${total} Correct`;
// Approximate IQ calculation
// This is a simplified model for entertainment purposes.
// Real IQ is normalized against a population.
const meanScoreRatio = { easy: 0.6, medium: 0.5, hard: 0.4 };
const sd = 0.15; // standard deviation for score ratio
const userRatio = score / total;
const meanRatio = meanScoreRatio[gameState.difficulty];
const zScore = (userRatio - meanRatio) / sd;
const iq = Math.round(100 + zScore * 15);
DOM.iqEstimate.textContent = iq;
}
function restartTest() {
changeScreen(DOM.startScreen);
}
</script>
</body>
</html>
```# IQ Test Generator with Graphical Sequence Completion
I'll create a complete browser-based IQ test generator that displays 3×3 sequence-completion tasks with mathematical transformations, multiple difficulty levels, and IQ estimation.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Graphical IQ Test</title>
<style>
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
color: white;
margin: 0;
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 900px;
margin: 0 auto;
background: rgba(0, 0, 0, 0.7);
border-radius: 15px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
header {
text-align: center;
margin-bottom: 20px;
}
h1 {
font-size: 2.5rem;
margin-bottom: 5px;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
background: linear-gradient(to right, #ff8a00, #da1b60);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
font-size: 1.1rem;
opacity: 0.8;
margin-bottom: 20px;
}
.difficulty-selector {
display: flex;
justify-content: center;
gap: 15px;
margin: 20px 0;
}
.difficulty-btn {
padding: 12px 25px;
border: none;
border-radius: 50px;
background: linear-gradient(145deg, #3a7bd5, #00d2ff);
color: white;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
.difficulty-btn:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(0,0,0,0.4);
}
.difficulty-btn.active {
background: linear-gradient(145deg, #ff8a00, #da1b60);
box-shadow: 0 0 15px rgba(255,138,0,0.7);
}
.test-container {
display: none;
flex-direction: column;
align-items: center;
}
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin: 20px 0;
width: 100%;
max-width: 600px;
}
.grid-cell {
aspect-ratio: 1;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
box-shadow: inset 0 0 10px rgba(0,0,0,0.3);
}
.grid-cell.missing {
background: rgba(255, 255, 255, 0.05);
border: 2px dashed rgba(255,255,255,0.3);
}
.options-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
width: 100%;
max-width: 600px;
margin: 20px 0;
}
.option {
aspect-ratio: 1;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.option:hover {
transform: scale(1.05);
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 15px rgba(100, 200, 255, 0.5);
}
.option.selected {
transform: scale(1.08);
box-shadow: 0 0 20px rgba(0, 200, 255, 0.8);
border: 2px solid #00d2ff;
}
.progress-container {
width: 100%;
max-width: 600px;
margin: 20px 0;
}
.progress-bar {
height: 10px;
background: rgba(255, 255, 255, 0.1);
border-radius: 5px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #3a7bd5, #00d2ff);
border-radius: 5px;
width: 0%;
transition: width 0.5s ease;
}
.results {
display: none;
text-align: center;
padding: 30px;
}
.iq-display {
font-size: 5rem;
font-weight: bold;
margin: 20px 0;
background: linear-gradient(to right, #ff8a00, #da1b60);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.iq-description {
font-size: 1.2rem;
margin-bottom: 30px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.restart-btn {
padding: 12px 30px;
background: linear-gradient(145deg, #3a7bd5, #00d2ff);
color: white;
border: none;
border-radius: 50px;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s ease;
}
.restart-btn:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.status-bar {
display: flex;
justify-content: space-between;
width: 100%;
max-width: 600px;
margin: 10px 0;
}
.score-display {
font-size: 1.2rem;
font-weight: bold;
}
canvas {
max-width: 100%;
max-height: 100%;
}
footer {
text-align: center;
margin-top: 30px;
opacity: 0.7;
font-size: 0.9rem;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Graphical IQ Test</h1>
<p class="subtitle">Complete the 3×3 sequences by selecting the missing pattern</p>
</header>
<div class="difficulty-selector">
<button class="difficulty-btn" data-level="easy">Easy</button>
<button class="difficulty-btn" data-level="medium">Medium</button>
<button class="difficulty-btn" data-level="hard">Hard</button>
</div>
<div class="test-container" id="testContainer">
<div class="grid-container" id="gridContainer"></div>
<div class="options-container" id="optionsContainer"></div>
<div class="status-bar">
<div class="score-display">Score: <span id="scoreValue">0</span></div>
<div>Question: <span id="questionCount">1</span>/10</div>
</div>
<div class="progress-container">
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
</div>
</div>
<div class="results" id="resultsContainer">
<h2>Your IQ Estimate</h2>
<div class="iq-display" id="iqValue">115</div>
<p class="iq-description" id="iqDescription">Above average cognitive ability</p>
<button class="restart-btn" id="restartBtn">Take Test Again</button>
</div>
<footer>
<p>This test measures pattern recognition and logical reasoning abilities. Results are estimates only.</p>
</footer>
</div>
<script>
// DOM Elements
const difficultyButtons = document.querySelectorAll('.difficulty-btn');
const testContainer = document.getElementById('testContainer');
const gridContainer = document.getElementById('gridContainer');
const optionsContainer = document.getElementById('optionsContainer');
const resultsContainer = document.getElementById('resultsContainer');
const scoreValue = document.getElementById('scoreValue');
const questionCount = document.getElementById('questionCount');
const progressFill = document.getElementById('progressFill');
const iqValue = document.getElementById('iqValue');
const iqDescription = document.getElementById('iqDescription');
const restartBtn = document.getElementById('restartBtn');
// Test state
let currentDifficulty = null;
let score = 0;
let currentQuestion = 0;
let totalQuestions = 10;
let correctAnswerIndex = -1;
let selectedOption = null;
// Transformation functions for pattern generation
const transformations = {
// Shape transformations
circle: (ctx, x, y, size, params) => {
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
},
square: (ctx, x, y, size, params) => {
ctx.fillRect(x - size, y - size, size * 2, size * 2);
},
triangle: (ctx, x, y, size, params) => {
ctx.beginPath();
ctx.moveTo(x, y - size);
ctx.lineTo(x + size, y + size);
ctx.lineTo(x - size, y + size);
ctx.closePath();
ctx.fill();
},
star: (ctx, x, y, size, params) => {
ctx.beginPath();
for (let i = 0; i < 5; i++) {
const angle = (i * 2 * Math.PI / 5) - Math.PI / 2;
const outerX = x + size * Math.cos(angle);
const outerY = y + size * Math.sin(angle);
if (i === 0) ctx.moveTo(outerX, outerY);
else ctx.lineTo(outerX, outerY);
const innerAngle = angle + Math.PI / 5;
const innerX = x + size * 0.4 * Math.cos(innerAngle);
const innerY = y + size * 0.4 * Math.sin(innerAngle);
ctx.lineTo(innerX, innerY);
}
ctx.closePath();
ctx.fill();
},
// Color transformations
colorShift: (baseColor, row, col) => {
const hueShift = (row * 40 + col * 20) % 360;
return `hsl(${(baseColor + hueShift) % 360}, 80%, 60%)`;
},
// Size transformations
sizeScale: (baseSize, row, col) => {
return baseSize * (0.8 + 0.2 * (row + col));
},
// Rotation transformations
rotation: (baseRotation, row, col) => {
return baseRotation + (row * 30 + col * 15);
},
// Position transformations
positionOffset: (baseX, baseY, row, col) => {
return {
x: baseX + (col - 1) * 15,
y: baseY + (row - 1) * 15
};
}
};
// Initialize the test
function initTest() {
score = 0;
currentQuestion = 0;
scoreValue.textContent = score;
questionCount.textContent = "1";
progressFill.style.width = "0%";
testContainer.style.display = "flex";
resultsContainer.style.display = "none";
generateQuestion();
}
// Generate a new question
function generateQuestion() {
currentQuestion++;
questionCount.textContent = currentQuestion;
progressFill.style.width = `${(currentQuestion-1)/totalQuestions*100}%`;
// Clear containers
gridContainer.innerHTML = '';
optionsContainer.innerHTML = '';
// Generate sequence pattern
const pattern = generatePattern();
// Create grid cells
for (let row = 0; row < 3; row++) {
for (let col = 0; col < 3; col++) {
const cell = document.createElement('div');
cell.className = 'grid-cell';
if (row === 2 && col === 2) {
cell.classList.add('missing');
cell.textContent = '?';
} else {
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
cell.appendChild(canvas);
drawPattern(canvas, pattern, row, col);
}
gridContainer.appendChild(cell);
}
}
// Generate answer options
generateOptions(pattern);
}
// Generate pattern based on difficulty
function generatePattern() {
const pattern = {
baseShape: ['circle', 'square', 'triangle', 'star'][Math.floor(Math.random() * 4)],
baseColor: Math.floor(Math.random() * 360),
baseSize: 20 + Math.floor(Math.random() * 15),
baseRotation: Math.floor(Math.random() * 360),
transformations: []
};
// Add transformations based on difficulty
if (currentDifficulty === 'easy') {
pattern.transformations.push('colorShift');
} else if (currentDifficulty === 'medium') {
pattern.transformations.push('colorShift');
pattern.transformations.push('sizeScale');
} else {
pattern.transformations.push('colorShift');
pattern.transformations.push('sizeScale');
pattern.transformations.push('rotation');
pattern.transformations.push('positionOffset');
}
return pattern;
}
// Draw pattern in a grid cell
function drawPattern(canvas, pattern, row, col) {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Calculate position
let x = canvas.width / 2;
let y = canvas.height / 2;
let size = pattern.baseSize;
let color = pattern.baseColor;
let rotation = pattern.baseRotation;
// Apply transformations
pattern.transformations.forEach(transformation => {
if (transformation === 'colorShift') {
color = transformations.colorShift(pattern.baseColor, row, col);
} else if (transformation === 'sizeScale') {
size = transformations.sizeScale(pattern.baseSize, row, col);
} else if (transformation === 'rotation') {
rotation = transformations.rotation(pattern.baseRotation, row, col);
} else if (transformation === 'positionOffset') {
const pos = transformations.positionOffset(x, y, row, col);
x = pos.x;
y = pos.y;
}
});
// Draw the shape
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation * Math.PI / 180);
ctx.fillStyle = typeof color === 'string' ? color : `hsl(${color}, 80%, 60%)`;
transformations[pattern.baseShape](ctx, 0, 0, size);
ctx.restore();
}
// Generate answer options
function generateOptions(pattern) {
const options = [];
// Generate correct answer
const correctCanvas = document.createElement('canvas');
correctCanvas.width = 100;
correctCanvas.height = 100;
drawPattern(correctCanvas, pattern, 2, 2);
options.push(correctCanvas);
// Generate incorrect options
for (let i = 0; i < 3; i++) {
const incorrectPattern = JSON.parse(JSON.stringify(pattern));
// Modify pattern to create plausible distractors
if (Math.random() > 0.5) {
// Change base shape
const shapes = ['circle', 'square', 'triangle', 'star'].filter(s => s !== incorrectPattern.baseShape);
incorrectPattern.baseShape = shapes[Math.floor(Math.random() * shapes.length)];
} else {
// Modify transformation parameters
if (incorrectPattern.transformations.includes('colorShift')) {
incorrectPattern.baseColor = (incorrectPattern.baseColor + 120) % 360;
}
if (incorrectPattern.transformations.includes('sizeScale')) {
incorrectPattern.baseSize = incorrectPattern.baseSize * (0.7 + Math.random() * 0.6);
}
if (incorrectPattern.transformations.includes('rotation')) {
incorrectPattern.baseRotation = (incorrectPattern.baseRotation + 90) % 360;
}
}
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
drawPattern(canvas, incorrectPattern, 2, 2);
options.push(canvas);
}
// Shuffle options
shuffleArray(options);
// Add options to container
options.forEach((canvas, index) => {
const option = document.createElement('div');
option.className = 'option';
option.appendChild(canvas);
// Store correct answer index
if (canvas === correctCanvas) {
correctAnswerIndex = index;
}
option.addEventListener('click', () => selectOption(option, index));
optionsContainer.appendChild(option);
});
}
// Select an option
function selectOption(option, index) {
// Remove previous selection
if (selectedOption) {
selectedOption.classList.remove('selected');
}
// Set new selection
option.classList.add('selected');
selectedOption = option;
// Check answer after a short delay
setTimeout(() => checkAnswer(index), 500);
}
// Check if answer is correct
function checkAnswer(index) {
if (index === correctAnswerIndex) {
// Correct answer
score += currentDifficulty === 'easy' ? 1 :
currentDifficulty === 'medium' ? 2 : 3;
scoreValue.textContent = score;
}
// Move to next question or show results
if (currentQuestion < totalQuestions) {
generateQuestion();
} else {
showResults();
}
}
// Show final results
function showResults() {
testContainer.style.display = 'none';
resultsContainer.style.display = 'block';
// Calculate IQ estimate
const maxScore = totalQuestions * 3; // 3 points for hard questions
const iq = Math.floor(80 + (score / maxScore) * 40); // Scale to 80-120
iqValue.textContent = iq;
// Set IQ description
let description = '';
if (iq < 90) {
description = 'Below average cognitive ability';
} else if (iq < 110) {
description = 'Average cognitive ability';
} else if (iq < 130) {
description = 'Above average cognitive ability';
} else {
description = 'Superior cognitive ability';
}
iqDescription.textContent = description;
}
// Utility function to shuffle array
function shuffleArray(array) {
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]];
}
}
// Event listeners
difficultyButtons.forEach(button => {
button.addEventListener('click', () => {
// Set active button
difficultyButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
// Set difficulty and start test
currentDifficulty = button.dataset.level;
initTest();
});
});
restartBtn.addEventListener('click', () => {
resultsContainer.style.display = 'none';
difficultyButtons.forEach(btn => btn.classList.remove('active'));
});
</script>
</body>
</html>
```
## Features Implemented
1. **Graphical Sequence Completion Tasks**:
- Generates 3×3 grids with mathematical pattern sequences
- Uses canvas to render geometric shapes with transformations
- The bottom-right cell is always missing and needs completion
2. **Mathematical Transformations**:
- Shape transformations (circle, square, triangle, star)
- Color transformations using HSL color space
- Size scaling based on position
- Rotation transformations
- Position offsets
3. **Difficulty Levels**:
- Easy: Simple color transformations
- Medium: Color and size transformations
- Hard: All transformations including rotation and position
4. **Multiple-Choice Selection**:
- Four options for the missing ninth image
- One correct answer and three plausible distractors
- Visual feedback when selecting an option
5. **Scoring and IQ Estimation**:
- Tracks user responses and scores
- Different points based on difficulty level
- Calculates IQ estimate (80-120 range) after 10 questions
- Provides IQ description based on score
6. **User Interface**:
- Modern gradient-based design with dark theme
- Progress bar showing test advancement
- Responsive layout that works on different screen sizes
- Visual feedback for selections and interactions
The test runs entirely in the browser with no external dependencies. All pattern generation and transformation logic is implemented using JavaScript canvas API and mathematical functions.```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Browser IQ Test Generator</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 20px; }
#difficultySelection, #testArea, #resultArea { margin: 20px auto; }
button { margin: 5px; padding: 10px 20px; font-size: 16px; }
#grid { display: grid; grid-template-columns: repeat(3, 100px); grid-gap: 10px; justify-content: center; margin-bottom: 20px; }
.grid-canvas { border: 1px solid #ccc; }
#options { display: grid; grid-template-columns: repeat(auto-fit, 80px); grid-gap: 10px; justify-content: center; }
.option-canvas { border: 1px solid #ccc; cursor: pointer; }
.option-canvas:hover { border-color: #666; }
</style>
</head>
<body>
<h1>Browser IQ Test</h1>
<div id="difficultySelection">
<p>Select Difficulty Level:</p>
<button id="easyBtn">Easy</button>
<button id="medBtn">Medium</button>
<button id="hardBtn">Hard</button>
</div>
<div id="testArea" style="display:none;">
<p id="questionNumber"></p>
<div id="grid"></div>
<div id="options"></div>
</div>
<div id="resultArea" style="display:none;"></div>
<script>
// Utility functions
function randomChoice(arr){ return arr[Math.floor(Math.random()*arr.length)]; }
function shuffle(arr){
for(let i=arr.length-1; i>0; i--){
let j = Math.floor(Math.random()*(i+1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
function applyTransform(params, type, delta){
let p = Object.assign({}, params);
if(type === "rotation"){
p.rotation = (p.rotation + delta) % 360;
} else if(type === "scale"){
p.scale = p.scale * delta;
} else if(type === "hue"){
p.hue = (p.hue + delta) % 360;
}
return p;
}
// Drawing function
function drawShape(ctx, params){
const w = ctx.canvas.width, h = ctx.canvas.height;
ctx.clearRect(0,0,w,h);
ctx.fillStyle = `hsl(${params.hue},60%,50%)`;
ctx.save();
ctx.translate(w/2, h/2);
ctx.rotate(params.rotation * Math.PI/180);
ctx.scale(params.scale, params.scale);
let sides = params.shape === "triangle" ? 3 : params.shape === "square" ? 4 : 5;
let radius = Math.min(w, h)/4;
ctx.beginPath();
for(let i=0; i<sides; i++){
let angle = (i * 2 * Math.PI / sides) - Math.PI/2;
let x = radius * Math.cos(angle), y = radius * Math.sin(angle);
if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
}
ctx.closePath();
ctx.fill();
ctx.restore();
}
// Question generation
const shapeTypes = ["triangle","square","pentagon"];
const transformTypes = ["rotation","scale","hue"];
function generateQuestion(){
// Pick two distinct transformations
let T1 = randomChoice(transformTypes);
let T2 = randomChoice(transformTypes.filter(t=>t!==T1));
let delta1 = T1==="rotation"?90: T1==="scale"?1.5: 60;
let delta2 = T2==="rotation"?90: T2==="scale"?1.5: 60;
// Build a 3x3 grid of parameter objects
let gridParams = [];
for(let row=0; row<3; row++){
let base = {
shape: randomChoice(shapeTypes),
rotation: [0,90,180,270][Math.floor(Math.random()*4)],
scale: 1,
hue: Math.floor(Math.random()*360)
};
let p0 = base;
let p1 = applyTransform(p0, T1, delta1);
let p2 = applyTransform(p1, T2, delta2);
gridParams.push(p0, p1, p2);
}
// Save correct answer (last cell) and blank it
let correct = gridParams[8];
gridParams[8] = null;
// Generate multiple-choice options
let options = [correct];
while(options.length < 6){
let d = {
shape: randomChoice(shapeTypes),
rotation: [0,90,180,270][Math.floor(Math.random()*4)],
scale: [0.75,1,1.25,1.5][Math.floor(Math.random()*4)],
hue: Math.floor(Math.random()*360)
};
// avoid duplicates & correct
let dup = options.some(o=>
o.shape===d.shape && o.rotation===d.rotation &&
Math.abs(o.scale-d.scale)<0.01 && o.hue===d.hue
);
if(!dup) options.push(d);
}
shuffle(options);
return { grid: gridParams, answer: correct, options: options };
}
// State
let questions = [], currentIndex = 0, correctCount = 0;
let difficulty = "", numQuestions = 0, expectedSuccess = 0;
// UI Handlers
function startTest(){
document.getElementById("difficultySelection").style.display = "none";
document.getElementById("testArea").style.display = "block";
questions = []; currentIndex = 0; correctCount = 0;
if(difficulty==="Easy"){ numQuestions=5; expectedSuccess=0.9; }
else if(difficulty==="Medium"){ numQuestions=7; expectedSuccess=0.7; }
else { numQuestions=10; expectedSuccess=0.5; }
for(let i=0; i<numQuestions; i++) questions.push(generateQuestion());
showQuestion();
}
function showQuestion(){
let q = questions[currentIndex];
document.getElementById("questionNumber").innerText =
`Question ${currentIndex+1} of ${numQuestions}`;
// Draw grid
let gridDiv = document.getElementById("grid");
gridDiv.innerHTML = "";
for(let i=0; i<9; i++){
let c = document.createElement("canvas");
c.width = 100; c.height = 100; c.className = "grid-canvas";
gridDiv.appendChild(c);
if(q.grid[i]) drawShape(c.getContext("2d"), q.grid[i]);
}
// Draw options
let optionsDiv = document.getElementById("options");
optionsDiv.innerHTML = "";
q.options.forEach(opt=>{
let c = document.createElement("canvas");
c.width = 80; c.height = 80; c.className = "option-canvas";
drawShape(c.getContext("2d"), opt);
c.addEventListener("click", ()=>{
let ok =
opt.shape===q.answer.shape &&
opt.rotation===q.answer.rotation &&
Math.abs(opt.scale-q.answer.scale)<0.01 &&
opt.hue===q.answer.hue;
if(ok) correctCount++;
currentIndex++;
if(currentIndex < numQuestions) showQuestion();
else showResult();
});
optionsDiv.appendChild(c);
});
}
function showResult(){
document.getElementById("testArea").style.display = "none";
let resDiv = document.getElementById("resultArea");
resDiv.style.display = "block";
let successRate = correctCount/numQuestions;
let IQ = Math.round(100 + (successRate - expectedSuccess)*(15/(1-expectedSuccess)));
IQ = Math.max(70, Math.min(145, IQ));
resDiv.innerHTML = `
<h2>Test Complete</h2>
<p>Your score: ${correctCount} / ${numQuestions}</p>
<p>Estimated IQ: ${IQ}</p>
<button id="retake">Retake Test</button>
`;
document.getElementById("retake").addEventListener("click", ()=>{
resDiv.style.display = "none";
document.getElementById("difficultySelection").style.display = "block";
});
}
// Setup
window.onload = function(){
document.getElementById("easyBtn").onclick = ()=>{ difficulty="Easy"; startTest(); };
document.getElementById("medBtn").onclick = ()=>{ difficulty="Medium"; startTest(); };
document.getElementById("hardBtn").onclick = ()=>{ difficulty="Hard"; startTest(); };
};
</script>
</body>
</html>
``````html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>In-Browser IQ Test Generator</title>
<style>
body{font-family:Arial,Helvetica,sans-serif;background:#f4f4f8;margin:0;display:flex;flex-direction:column;align-items:center}
h1{margin-top:20px}
#screen{max-width:900px;width:100%;padding:20px;box-sizing:border-box}
#matrix{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:20px 0}
#options{display:flex;justify-content:space-around;flex-wrap:wrap}
.optBtn{border:2px solid #444;border-radius:4px;background:#fff;cursor:pointer;margin:6px;padding:0}
.optBtn.selected{border-color:#1e90ff}
#startBtn,#nextBtn,#againBtn{padding:10px 24px;font-size:16px;border:none;border-radius:4px;background:#1e90ff;color:#fff;cursor:pointer}
#difficultySel{font-size:16px;padding:6px}
#resultBox{font-size:20px;margin:20px 0}
canvas{display:block}
</style>
</head>
<body>
<h1>Mini Raven-style IQ Test</h1>
<div id="screen">
<div id="setup">
<p>Select a difficulty and press Start.</p>
<select id="difficultySel">
<option value="1">Easy</option>
<option value="2">Medium</option>
<option value="3">Hard</option>
</select>
<button id="startBtn">Start</button>
</div>
<div id="quiz" style="display:none">
<div id="qCounter"></div>
<div id="matrix"></div>
<h3>Pick the missing image:</h3>
<div id="options"></div>
<button id="nextBtn" disabled>Next</button>
</div>
<div id="result" style="display:none">
<div id="resultBox"></div>
<button id="againBtn">Take Again</button>
</div>
</div>
<script>
//------------- configuration -------------
const PUZZLES_PER_TEST = 10;
const CANVAS_SIZE = 90;
//------------- state ---------------------
let difficulty=1,currentPuzzle=0,score=0,selectedOpt=-1,puzzles=[];
//------------- helpers -------------------
function randInt(n){return Math.floor(Math.random()*n);}
function copy(o){return JSON.parse(JSON.stringify(o));}
//------------- drawing -------------------
function drawShape(ctx,attr){
const {shape,color,hue,rotation} = attr;
const size = CANVAS_SIZE*0.5;
ctx.clearRect(0,0,CANVAS_SIZE,CANVAS_SIZE);
ctx.save();
ctx.translate(CANVAS_SIZE/2, CANVAS_SIZE/2);
ctx.rotate(rotation*Math.PI/180);
ctx.fillStyle = `hsl(${hue},70%,60%)`;
ctx.beginPath();
switch(shape){
case 'circle':
ctx.arc(0,0,size/1.5,0,Math.PI*2);break;
case 'square':
ctx.rect(-size/2,-size/2,size,size);break;
case 'triangle':
ctx.moveTo(0,-size/1.1);
ctx.lineTo(size/1.1,size/1.1);
ctx.lineTo(-size/1.1,size/1.1);
ctx.closePath();
break;
case 'pentagon':
for(let i=0;i<5;i++){
const ang = (i*72-90)*Math.PI/180;
ctx.lineTo(Math.cos(ang)*size/1.2,Math.sin(ang)*size/1.2);
}
ctx.closePath();break;
}
ctx.fill();
ctx.restore();
}
function canvasToDataURL(attr){
const cv = document.createElement('canvas');
cv.width=cv.height=CANVAS_SIZE;
drawShape(cv.getContext('2d'),attr);
return cv.toDataURL();
}
//------------- puzzle generation ----------
function generatePuzzle(diff){
const shapes = ['circle','square','triangle','pentagon'];
// choose which attributes will have systematic transformations
const attrsPool = ['shape','hue','rotation'];
const usedAttrs = [];
while(usedAttrs.length<diff){
let a = attrsPool[randInt(attrsPool.length)];
if(!usedAttrs.includes(a))usedAttrs.push(a);
}
// base attribute values
const base={
shape:shapes[randInt(shapes.length)],
hue:randInt(360),
rotation: [0,45,90,135,180,225,270,315][randInt(8)]
};
// increments
const inc={
shape:1+hypersign(),
hue:30*(1+randInt(5)),
rotation:45
};
function hypersign(){return randInt(2)?1:2;}
// grid attributes
const grid=[];
for(let r=0;r<3;r++){
for(let c=0;c<3;c++){
if(r===2&&c===2)continue; // missing
const a=copy(base);
if(usedAttrs.includes('shape')){
const idx=(shapes.indexOf(base.shape)+ r + c)%shapes.length;
a.shape=shapes[idx];
}
if(usedAttrs.includes('hue')){
a.hue=(base.hue + inc.hue*(r+c))%360;
}
if(usedAttrs.includes('rotation')){
a.rotation=(base.rotation + inc.rotation*(r*c))%360;
}
grid.push(a);
}
}
// correct answer attributes (for 2,2 cell)
const correct=copy(base);
if(usedAttrs.includes('shape')){
correct.shape=shapes[(shapes.indexOf(base.shape)+2+2)%shapes.length];
}
if(usedAttrs.includes('hue')){
correct.hue=(base.hue + inc.hue*(4))%360;
}
if(usedAttrs.includes('rotation')){
correct.rotation=(base.rotation + inc.rotation*(4))%360;
}
// options
const options=[correct];
while(options.length<4){
const d=copy(correct);
const changeAttr=usedAttrs[randInt(usedAttrs.length)];
if(changeAttr==='shape'){
d.shape=shapes[randInt(shapes.length)];
}else if(changeAttr==='hue'){
d.hue=(d.hue+inc.hue*(1+randInt(4)))%360;
}else if(changeAttr==='rotation'){
d.rotation=(d.rotation+inc.rotation*(1+randInt(3)))%360;
}
// uniqueness
if(!options.some(x=>JSON.stringify(x)===JSON.stringify(d)))options.push(d);
}
// shuffle options
for(let i=options.length-1;i>0;i--){
const j=randInt(i+1); [options[i],options[j]]=[options[j],options[i]];
}
const correctIndex=options.findIndex(o=>JSON.stringify(o)===JSON.stringify(correct));
return {grid,options,correctIndex};
}
//------------- UI rendering ---------------
const matrixDiv = document.getElementById('matrix');
const optionsDiv = document.getElementById('options');
const qCounter = document.getElementById('qCounter');
const nextBtn = document.getElementById('nextBtn');
function renderPuzzle(p){
matrixDiv.innerHTML='';
// draw first 8 cells
p.grid.forEach(attr=>{
const img=document.createElement('img');
img.src=canvasToDataURL(attr);
img.width=CANVAS_SIZE;img.height=CANVAS_SIZE;
matrixDiv.appendChild(img);
});
// placeholder for missing cell
const place=document.createElement('div');
place.style.width=CANVAS_SIZE+'px';
place.style.height=CANVAS_SIZE+'px';
place.style.border='3px dashed #999';
place.style.display='flex';
place.style.alignItems='center';
place.style.justifyContent='center';
place.textContent='?';
place.style.fontSize='36px';
matrixDiv.appendChild(place);
// options
optionsDiv.innerHTML='';
p.options.forEach((attr,idx)=>{
const btn=document.createElement('button');
btn.className='optBtn';
const img=document.createElement('img');
img.src=canvasToDataURL(attr);
img.width=CANVAS_SIZE;img.height=CANVAS_SIZE;
btn.appendChild(img);
btn.onclick=()=>{
[...optionsDiv.children].forEach(b=>b.classList.remove('selected'));
btn.classList.add('selected');
selectedOpt=idx;
nextBtn.disabled=false;
};
optionsDiv.appendChild(btn);
});
nextBtn.disabled=true;
selectedOpt=-1;
qCounter.textContent=`Question ${currentPuzzle+1} of ${PUZZLES_PER_TEST}`;
}
//------------- flow control ---------------
function startTest(){
difficulty=parseInt(document.getElementById('difficultySel').value,10);
currentPuzzle=0;score=0;puzzles=[];
for(let i=0;i<PUZZLES_PER_TEST;i++)puzzles.push(generatePuzzle(difficulty));
document.getElementById('setup').style.display='none';
document.getElementById('result').style.display='none';
document.getElementById('quiz').style.display='';
renderPuzzle(puzzles[0]);
}
function nextStep(){
if(selectedOpt===puzzles[currentPuzzle].correctIndex)score++;
currentPuzzle++;
if(currentPuzzle<PUZZLES_PER_TEST){
renderPuzzle(puzzles[currentPuzzle]);
}else{
endTest();
}
}
function endTest(){
document.getElementById('quiz').style.display='none';
const pct=score/PUZZLES_PER_TEST;
const estIQ = Math.round(80+ pct*40); // crude estimate 80-120
document.getElementById('resultBox').innerHTML=
`You answered ${score} / ${PUZZLES_PER_TEST} correctly.<br>
Estimated IQ: <strong>${estIQ}</strong> (±10).<br>
This is an informal approximation, not a clinical measurement.`;
document.getElementById('result').style.display='';
}
//------------- hooks ----------------------
document.getElementById('startBtn').onclick=startTest;
nextBtn.onclick=nextStep;
document.getElementById('againBtn').onclick=()=>{document.getElementById('setup').style.display='';document.getElementById('result').style.display='none';};
</script>
</body>
</html>
``````html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>IQ Test Generator</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700" rel="stylesheet">
<style>
body {
font-family: 'Roboto', sans-serif;
background: #f7f9fa;
color: #222;
margin: 0;
padding: 0;
min-height: 100vh;
}
.container {
max-width: 600px;
margin: 30px auto;
background: #fff;
border-radius: 16px;
box-shadow: 0 6px 24px rgba(60,90,120,0.10), 0 1.5px 6px rgba(0,0,0,0.03);
padding: 24px 20px 30px 20px;
}
h1 {
font-size: 2em;
font-weight: 700;
margin-bottom: 0.35em;
letter-spacing: -1px;
}
.settings, .stats, .question, .final-score {
margin-top: 25px;
text-align: center;
}
.difficulty-btn {
font-size: 1em;
padding: 10px 30px;
margin: 12px 8px;
border: none;
border-radius: 8px;
background: #e3eaf1;
color: #283848;
font-weight: 600;
cursor: pointer;
transition: background 0.18s;
}
.difficulty-btn.selected, .difficulty-btn:hover {
background: #54b6fa;
color: #fff;
}
.matrix-container {
display: grid;
gap: 10px;
grid-template-columns: repeat(3, 64px);
justify-content: center;
margin: 0 auto 22px auto;
background: #e8edf4;
border-radius: 12px;
padding: 18px 14px 7px 14px;
min-width: 230px;
}
.matrix-cell {
background: #fff;
border-radius: 8px;
border: 1px solid #e3e8ed;
width: 64px; height: 64px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.matrix-cell.missing {
background: #e0e0e0;
border: 2px dashed #a0a0a0;
}
.choices {
display: grid;
grid-template-columns: repeat(2, 110px);
gap: 14px;
justify-content: center;
align-items: center;
margin-bottom: 8px;
}
.choice-btn {
background: #f8fafb;
border: 1.5px solid #bcd1e6;
border-radius: 11px;
padding: 5px 3px 2px 3px;
cursor: pointer;
transition: border 0.14s, box-shadow 0.14s;
min-width: 110px; min-height: 77px;
display: flex; align-items: center; justify-content: center;
}
.choice-btn.selected, .choice-btn:hover {
border: 2.5px solid #47a0ea;
background: #e6f1fb;
box-shadow: 0 0 8px #74bffe40;
}
.stats {
font-size: 1.04em;
color: #55698e;
margin-top: 3px;
margin-bottom: 9px;
}
.submit-btn, .restart-btn {
margin-top: 16px;
padding: 12px 38px;
background: #36b37e;
color: #fff;
font-size: 1.09em;
font-weight: 700;
border: none;
border-radius: 12px;
cursor: pointer;
transition: background 0.13s;
box-shadow: 0 1.5px 6px #36b37e20;
}
.submit-btn:disabled {
background: #b9e6d2;
color: #97b6ac;
cursor: default;
}
.restart-btn {
background: #2590d1;
margin-left: 8px;
}
.final-score {
font-size: 1.23em;
}
.final-score .iq {
font-weight: 700;
font-size: 1.9em;
color: #36b37e;
letter-spacing: 0.5px;
}
@media (max-width: 700px) {
.container { max-width: 100%; }
.matrix-container { min-width: 0; }
}
@media (max-width: 550px) {
.matrix-container {grid-template-columns: repeat(3, 48px);}
.matrix-cell { width: 48px; height: 48px;}
.choices {grid-template-columns: 1fr 1fr; gap: 7px;}
.choice-btn { min-width: 60px; min-height: 45px; padding: 3px 1px;}
}
</style>
</head>
<body>
<div class="container">
<h1>IQ Test Generator</h1>
<div class="settings" id="settings">
<div>Select difficulty:</div>
<button class="difficulty-btn" data-diff="easy">Easy</button>
<button class="difficulty-btn" data-diff="medium">Medium</button>
<button class="difficulty-btn" data-diff="hard">Hard</button>
<div style="margin-top:14px; color:#6c7fa2; font-size:0.97em;">Each test has 10 3×3 pattern tasks</div>
</div>
<div class="question" id="question" style="display:none"></div>
<div class="final-score" id="final" style="display:none"></div>
</div>
<script>
const DIFFICULTY_SETTINGS = {
easy: {
transforms: ['rotate', 'size', 'count', 'shape'],
rounds: 10,
timePerQ: 36, // seconds
distractorDeviation: 1,
},
medium: {
transforms: ['rotate', 'size', 'count', 'shape', 'flip', 'fill'],
rounds: 10,
timePerQ: 43,
distractorDeviation: 1.5,
},
hard: {
transforms: ['rotate', 'size', 'count', 'shape', 'flip', 'fill', 'arithmetic', 'composition'],
rounds: 10,
timePerQ: 59,
distractorDeviation: 2.2,
}
};
const SHAPES = ['circle', 'square', 'triangle', 'diamond', 'pentagon'];
const FILLS = ['solid', 'hollow'];
const COLORS = ['#3590fa', '#f23a3c', '#f2ad22', '#24b363', '#b05fd9', '#000'];
let userData = {
difficulty: 'easy',
questions: [],
responses: [],
IQ: 100,
startTime: 0
};
const $ = (id) => document.getElementById(id);
// =================== Settings UI logic ====================
const settingsDiv = $('settings');
Array.from(settingsDiv.querySelectorAll('.difficulty-btn')).forEach(btn => {
btn.onclick = (e) => {
Array.from(settingsDiv.querySelectorAll('.difficulty-btn')).forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
userData.difficulty = btn.dataset.diff;
setTimeout(startTest, 350);
}
});
// =========== Canv Generation Helpers =======================
function getRandomItem(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function randInt(n, m) {
return Math.floor(Math.random() * (m - n + 1)) + n;
}
function shuffle(arr) {
for(let i = arr.length-1; i>0; --i) {
let j = randInt(0, i);
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
function deepCopy(obj) { // Safe for our params
return JSON.parse(JSON.stringify(obj));
}
// ============= Single shape draw ===========================
function drawShape(ctx, shape, x, y, r, fillType, color, rotation=0) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation*Math.PI/180);
ctx.beginPath();
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = 3;
// All shapes fit in a max radius = r.
switch(shape) {
case 'circle':
ctx.arc(0,0,r,0,2*Math.PI);
break;
case 'square':
ctx.moveTo(-r,-r); ctx.lineTo(r,-r); ctx.lineTo(r,r); ctx.lineTo(-r,r); ctx.closePath();
break;
case 'triangle':
let h = r*Math.sqrt(3);
ctx.moveTo(0,-r);
ctx.lineTo(h/2, r/2);
ctx.lineTo(-h/2, r/2);
ctx.closePath();
break;
case 'diamond':
ctx.moveTo(0,-r);
ctx.lineTo(r,0); ctx.lineTo(0,r); ctx.lineTo(-r,0); ctx.closePath();
break;
case 'pentagon':
for(let i=0;i<5;++i){
let angle = Math.PI/2 + i*2*Math.PI/5;
let xi = Math.cos(angle)*r;
let yi = Math.sin(angle)*r;
(i===0) ? ctx.moveTo(xi,yi) : ctx.lineTo(xi,yi);
} ctx.closePath(); break;
}
if (fillType === 'solid') ctx.fill();
else ctx.stroke();
ctx.restore();
}
// ================ Compose a cell graphic ====================
function cellToCanvas({
shape = 'circle',
size = 18,
count = 1,
orientation = 0,
fill = 'solid',
color = '#3590fa',
flipH = false,
extra = undefined // for composite
}, W=64, H=64) {
let canvas = document.createElement('canvas');
canvas.width = W; canvas.height = H;
let ctx = canvas.getContext('2d');
// Multiple count = radial arrangement
let angleInc = (360/count), angle0 = -90;
for (let i=0;i<count;i++) {
let a = angle0 + i*angleInc;
ctx.save();
ctx.translate(W/2, H/2);
if (flipH) ctx.scale(-1,1);
ctx.rotate((orientation+0)/180*Math.PI);
ctx.rotate(a*Math.PI/180);
ctx.translate(0, -((count > 1) ? size+6 : 0));
if (extra) { // composite
extra.forEach(spec=>drawShape(ctx, ...spec));
} else {
drawShape(ctx, shape, 0, 0, size, fill, color);
}
ctx.restore();
}
return canvas;
}
// ============== Pattern Generation Logic =====================
function generatePattern(transformList, difficulty) {
// Select one or two transforms based on difficulty
let nTransforms = (difficulty==='easy'? 1 : (difficulty==='medium'?randInt(1,2):randInt(2,2)));
shuffle(transformList);
let chosen = transformList.slice(0, nTransforms);
// Choose parameters for each
// Starting state
let start = {
shape: getRandomItem(SHAPES),
size: randInt(10,19),
count: randInt(1, (difficulty==='easy'?2: (difficulty==='medium'?3:4))),
orientation: [0,90,180,270][randInt(0,3)],
fill: getRandomItem(FILLS),
color: getRandomItem(COLORS),
flipH: false
};
// For 'composition', base shape, then add+
let composite = false; let compSpec = null;
// Build grid with rules
let grid = Array.from({length:3}, ()=> Array(3).fill(null));
// Determine step for each transform
let tfParams = {};
chosen.forEach(tf => {
switch(tf) {
case 'rotate':
tfParams.rotate = [ [0, randInt(30, 90)], [0, randInt(30,90)] ];
break;
case 'size':
tfParams.size = [ [0, randInt(3,8)], [0, randInt(2,9)] ];
break;
case 'count':
tfParams.count = [ [0, randInt(1,2)], [0, randInt(1,2)] ];
break;
case 'shape':
{
let shapeIdx = SHAPES.indexOf(start.shape);
tfParams.shape = [ [0, 1], [0, 1] ];
}
break;
case 'fill':
tfParams.fill = [ [null, getRandomItem(FILLS)], [null, getRandomItem(FILLS)] ];
break;
case 'flip':
tfParams.flipH = [ [false, true], [false, Math.random() > 0.5]];
break;
case 'arithmetic':
tfParams.size = [ [0, randInt(2,4)], [0, randInt(2,4)] ]; // changes in both axis
tfParams.count = [ [0, randInt(1,2)], [0, randInt(1,2)] ];
break;
case 'composition':
composite = true;
compSpec = [
[ getRandomItem(SHAPES), 0, 0, randInt(13,19), 'solid', getRandomItem(COLORS), 0 ],
[ getRandomItem(SHAPES), 0, 0, randInt(7,13), 'hollow', getRandomItem(COLORS), 0 ],
];
break;
}
});
// Compose cells
let tmpShapes = SHAPES.slice();
let f = (i,j) => {
let cell = deepCopy(start);
// Apply transform for first transform
chosen.forEach(tf => {
let param = tfParams[tf];
switch(tf) {
case 'rotate':
cell.orientation = start.orientation + i*param[0][1] + j*param[1][1];
break;
case 'size':
cell.size = Math.max(7, start.size + i*param[0][1] + j*param[1][1]);
break;
case 'count':
cell.count = Math.max(1, start.count + i*param[0][1] + j*param[1][1]);
break;
case 'shape':
let shapeIdx = SHAPES.indexOf(start.shape);
cell.shape = SHAPES[(shapeIdx + i*param[0][1] + j*param[1][1])%SHAPES.length];
break;
case 'fill':
if (i+j == 2) cell.fill = param[1][1]; // For only one
break;
case 'flip':
if (i+j >= 2) cell.flipH = param[1][1];
break;
case 'arithmetic':
cell.size = Math.max(8, start.size + i*param[0][1] - j*param[1][1]);
cell.count = Math.max(1, start.count + i*param[0][1] + j*param[1][1]);
break;
case 'composition':
cell.extra = compSpec;
break;
}
});
return cell;
}
// Build grid
for(let i=0;i<3;i++) for(let j=0;j<3;j++) grid[i][j]=f(i,j);
// Set missing slot
let missing = {i:2, j:2, cell:grid[2][2]};
grid[2][2] = null;
// Generate distractors
let distractors = [];
let nChoices = 4;
let correctIdx = randInt(0, nChoices-1);
for (let d=0; d<nChoices; ++d) {
if (d === correctIdx) {
distractors.push(deepCopy(missing.cell));
} else {
// Mutation from correct cell
let dist = DIFFICULTY_SETTINGS[difficulty].distractorDeviation;
let wrong = deepCopy(missing.cell);
let option = getRandomItem(chosen);
switch(option) {
case 'rotate':
wrong.orientation = (wrong.orientation + randInt(1,3)*30*dist)%360; break;
case 'size':
wrong.size = Math.max(7, wrong.size + randInt(-2,2)*dist); break;
case 'count':
wrong.count = Math.max(1, wrong.count + randInt(-1,2)*dist); break;
case 'shape':
let idx = SHAPES.indexOf(wrong.shape);
wrong.shape = SHAPES[(idx + randInt(1, SHAPES.length-1))%SHAPES.length]; break;
case 'fill':
wrong.fill = wrong.fill==='solid'?'hollow':'solid'; break;
case 'flip':
wrong.flipH = !wrong.flipH; break;
case 'arithmetic':
(Math.random()>0.5) ? (wrong.size = Math.max(9, wrong.size+randInt(-3,5))) :
(wrong.count = Math.max(1, wrong.count+randInt(-1,2)) ); break;
case 'composition':
// Swap order
if (wrong.extra) [wrong.extra[0],wrong.extra[1]] = [wrong.extra[1], wrong.extra[0]];
break;
}
distractors.push(wrong)
}
}
// Shuffle choices
shuffle(distractors);
// Solution idx
let solutionIdx = distractors.findIndex(d=>(JSON.stringify(d)===JSON.stringify(missing.cell)));
let pattern = {
grid, missing, distractors, chosen, solutionIdx
};
return pattern;
}
// ========== UI: Render Pattern as 3x3 Canvas grid + choices ========
function renderPattern(pattern, qidx, totalQ, selected = null, disabled=true) {
let out = `<div class="stats">Task ${qidx+1} of ${totalQ}</div>
<div class="matrix-container">`;
for(let i=0;i<3;i++) for(let j=0;j<3;j++) {
if(i===2&&j===2){
out += `<div class="matrix-cell missing"></div>`;
} else {
out += `<div class="matrix-cell">
<div style="text-align:center;">${canvasToImgTag(cellToCanvas(pattern.grid[i][j]))}</div>
</div>`;
}
}
out += '</div>';
// Choices
out += `<div style="font-size:1.10em;font-weight:600;margin-bottom:6px;">Select the missing image:</div>
<div class="choices">`;
pattern.distractors.forEach((cell, idx)=>{
let sel = (selected===idx)?'selected':'';
out += `<button class="choice-btn ${sel}" data-choice="${idx}" ${disabled? '':'tabindex="0"'}>${canvasToImgTag(cellToCanvas(cell, 66,66))}</button>`
});
out += `</div>`;
return out;
}
function canvasToImgTag(canvas) {
return `<img src="${canvas.toDataURL('image/png')}" width="${canvas.width}" height="${canvas.height}" alt="option" draggable="false" style="display:block;max-width:100%;margin:auto;">`;
}
// ==================== Test Logic ============================
function startTest() {
// Setup
settingsDiv.style.display = 'none';
userData.questions = [];
userData.responses = [];
userData.startTime = performance.now();
let difficulty = userData.difficulty;
let rounds = DIFFICULTY_SETTINGS[difficulty].rounds;
let tfList = DIFFICULTY_SETTINGS[difficulty].transforms;
// Generate all patterns in advance:
userData.questions = [];
for(let i=0; i<rounds; ++i){
userData.questions.push(generatePattern(tfList, difficulty));
}
nextQuestion(0);
}
function nextQuestion(qidx) {
const totalQ = userData.questions.length;
if (qidx >= totalQ) {
finishTest();
return;
}
let qdiv = $('question');
qdiv.style.display = '';
let pattern = userData.questions[qidx];
qdiv.innerHTML = renderPattern(pattern, qidx, totalQ, null, false) +
`<button class="submit-btn" id="submitbtn" disabled>Submit</button>`;
// Option logic
let selected = null;
let choices = Array.from(qdiv.querySelectorAll('.choice-btn'));
choices.forEach(btn => {
btn.onclick = e => {
selected = parseInt(btn.dataset.choice);
choices.forEach(b => b.classList.remove('selected'));
btn.classList.add('selected');
$('submitbtn').disabled = false;
}
});
$('submitbtn').onclick = () => {
// Save response time and correctness
let res = {
q: qidx,
correct: (selected === pattern.solutionIdx),
selected,
solutionIdx: pattern.solutionIdx,
t: Date.now()
};
userData.responses.push(res);
// Show answer feedback
let qdiv2 = $('question');
qdiv2.innerHTML = renderPattern(pattern, qidx, totalQ, selected, true)
+ `<button class="submit-btn" id="nextbtn">Next</button>`;
let choiceEls = qdiv2.querySelectorAll('.choice-btn');
choiceEls[pattern.solutionIdx].style.borderColor = '#36b37e';
choiceEls[pattern.solutionIdx].style.background = '#e7faee';
if (selected !== pattern.solutionIdx) {
choiceEls[selected].style.borderColor = '#f86353';
choiceEls[selected].style.background = '#fbe6e4';
}
qdiv2.querySelector('#nextbtn').onclick = () => nextQuestion(qidx+1);
};
}
// =========== IQ scoring (approximate, not clinical!) =========
function finishTest() {
$('question').style.display = 'none';
// Estimate score
let total = userData.questions.length;
let correct = userData.responses.filter(r=>r.correct).length;
let tScores = [];
for(let i=0; i<userData.responses.length; ++i) {
let s = userData.responses[i], prev = i===0?userData.startTime: userData.responses[i-1].t;
tScores.push( Math.max(1, (s.t-prev)/1000) );
}
// Basic model: for this kind of task, ~70% correct is "average",
// boost for hard mode, penalty for time
let diff = userData.difficulty;
let norm = (diff==='easy'?0.7:(diff==='medium'?0.54:0.39));
let acc = correct/total;
let avgTime = tScores.length ? (tScores.reduce((a,b)=>a+b,0)/tScores.length) : DIFFICULTY_SETTINGS[diff].timePerQ;
// IQ = 100 + (acc-norm)/0.08*15 + (bonus for hard) - (penalty for slow resp)
let bonus = (diff==='easy'?0:(diff==='medium'?2:6));
let penalty = 0;
if (avgTime > DIFFICULTY_SETTINGS[diff].timePerQ*0.95) penalty = (avgTime-DIFFICULTY_SETTINGS[diff].timePerQ)*3.8;
let estIQ = Math.round(100 + (acc-norm)/0.09*14 + bonus - penalty);
if (estIQ > 145) estIQ = randInt(140,147);
if (estIQ < 65) estIQ = randInt(64,70);
userData.IQ = estIQ;
// Feedback
let txt = `<div style="margin-bottom:16px;color:#3572a7;">Test complete!</div>
<div style="font-size:1.21em;">Accuracy: <b>${correct}/${total}</b> correct<br>
Avg. time per task: <b>${Math.round(avgTime*10)/10}s</b></div>
<div style="margin:25px 0 9px 0;">
<span>Your estimated IQ:</span>
<div class="iq">${estIQ}</div>
<div style="font-size:0.97em;color:#868c9b;margin-top:6px;">
(Range: 100 = average, 115 = 1SD above average, 130 = 2SD above average)
</div>
</div>
<button class="restart-btn" onclick="window.location.reload()">Restart Test</button>
`;
$('final').style.display = '';
$('final').innerHTML = txt;
}
</script>
</body>
</html>
```
```html
<!DOCTYPE html>
<html>
<head>
<title>Browser-Based IQ Test Generator</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
}
#grid {
display: grid;
grid-template-columns: repeat(3, 60px);
grid-gap: 10px;
margin: 20px auto 0;
}
#grid > div {
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ccc;
background: #f8f8f8;
}
canvas {
image-rendering: auto;
}
#options {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 20px 0;
}
.option-btn {
padding: 0;
border: 2px solid #ccc;
background: none;
cursor: pointer;
}
.option-btn:hover {
border-color: #888;
}
.option-btn.correct {
border-color: green;
}
.option-btn.incorrect {
border-color: red;
}
</style>
</head>
<body>
<div id="setup">
<h2>IQ Test Generator</h2>
<p>Select difficulty level and start the test:</p>
<label for="difficulty">Difficulty: </label>
<select id="difficulty">
<option value="easy">Easy</option>
<option value="medium">Medium</option>
<option value="hard">Hard</option>
</select>
<button onclick="startTest()" style="margin-left: 10px;">Start</button>
</div>
<div id="test" style="display:none;">
<h2>Question <span id="questionNumber"></span></h2>
<div id="grid"></div>
<div id="options"></div>
<button onclick="skipToResults()" id="skipButton" style="margin-top:15px;">Skip to Results</button>
</div>
<div id="results" style="display:none;">
<h2>Test Results</h2>
<p>Your estimated IQ score is <strong id="iqScore"></strong>.</p>
<p>This is calculated based on number of correct answers and relative difficulty level.</p>
<p><small>(This is a simplified IQ simulation. Actual IQ tests are far more complex and standardized.)</small></p>
<button onclick="location.reload()">Restart</button>
</div>
<script>
let currentQuestionIndex = 0;
let totalQuestions = 6;
let correctCount = 0;
let difficulty;
let questions = [];
// Helper functions
function statesEqual(a, b) {
return a.shape === b.shape && a.rotation === b.rotation && a.fill === b.fill;
}
// State transformations
function rotateRight(state) {
return {...state, rotation: (state.rotation + 90) % 360};
}
function flipColor(state) {
return {...state, fill: state.fill === 'black' ? 'white' : 'black'};
}
function nextShape(state) {
const shapes = ['circle', 'square', 'triangle'];
const idx = (shapes.indexOf(state.shape) + 1) % 3;
return {...state, shape: shapes[idx]};
}
function generateInitialState() {
const shapes = ['circle', 'square', 'triangle'];
return {
shape: shapes[Math.floor(Math.random() * 3)],
rotation: Math.floor(Math.random() * 4) * 90,
fill: Math.random() > 0.5 ? 'black' : 'white'
};
}
function getRuleFunction(difficulty) {
switch (difficulty) {
case 'easy':
const easyOpt = [rotateRight, flipColor, nextShape];
return easyOpt[Math.floor(Math.random() * 3)];
case 'medium':
const mediumOpt = [
(s) => rotateRight(flipColor(s)),
(s) => nextShape(rotateRight(s)),
(s) => flipColor(rotateRight(s))
];
return mediumOpt[Math.floor(Math.random() * 3)];
case 'hard':
return (state, i) => {
if (i % 2 === 0) return rotateRight(state);
return i % 3 === 1 ? nextShape(state) : flipColor(state);
};
default:
return rotateRight;
}
}
function generateStateSequence(ruleFunction, length = 9) {
let states = [generateInitialState()];
for (let i = 1; i < length; i++) {
states.push(ruleFunction(states[i-1], i));
}
return states;
}
function generateDistractors(correctState, count = 3) {
const distractors = [];
while (distractors.length < count) {
let variation = {...correctState};
const method = Math.floor(Math.random() * 3);
if (method === 0) {
// Change shape
const shapeOpt = ['circle', 'square', 'triangle'].filter(s => s !== variation.shape);
variation.shape = shapeOpt[Math.floor(Math.random() * 2)];
} else if (method === 1) {
// Change rotation
const rotationOpt = [0, 90, 180, 270].filter(r => r !== variation.rotation);
variation.rotation = rotationOpt[Math.floor(Math.random() * 3)];
} else {
// Change color
variation.fill = variation.fill === 'black' ? 'white' : 'black';
}
// Ensure uniqueness
if (!distractors.some(d => statesEqual(d, variation)) && !statesEqual(variation, correctState)) {
distractors.push(variation);
}
}
return distractors;
}
// Canvas rendering
function renderStateToCanvas(canvas, state) {
const ctx = canvas.getContext('2d');
const w = canvas.width;
const h = canvas.height;
// Clear canvas
ctx.clearRect(0, 0, w, h);
// Save context
ctx.save();
ctx.translate(w/2, h/2);
ctx.rotate(state.rotation * Math.PI / 180);
// Set fill color
ctx.fillStyle = state.fill;
// Draw shape
switch (state.shape) {
case 'circle':
ctx.beginPath();
ctx.arc(0, 0, w/3, 0, 2*Math.PI);
ctx.fill();
break;
case 'square':
ctx.fillRect(-w/3, -w/3, w*2/3, w*2/3);
break;
case 'triangle':
ctx.beginPath();
ctx.moveTo(0, -h/3);
ctx.lineTo(h/3, h/3);
ctx.lineTo(-h/3, h/3);
ctx.closePath();
ctx.fill();
break;
}
// Restore context
ctx.restore();
}
// Game functions
function startTest() {
difficulty = document.getElementById('difficulty').value;
document.getElementById('setup').style.display = 'none';
document.getElementById('test').style.display = 'block';
currentQuestionIndex = 0;
correctCount = 0;
questions = [];
showQuestion();
}
function showQuestion() {
document.getElementById('questionNumber').textContent = currentQuestionIndex + 1;
// Generate question
const rule = getRuleFunction(difficulty);
const fullSequence = generateStateSequence(rule, 9);
const correctState = fullSequence[8];
// Create question
const question = {
gridStates: fullSequence.slice(0, 8),
options: [correctState, ...generateDistractors(correctState)],
correctState: correctState
};
// Shuffle options
question.options.sort(() => Math.random() - 0.5);
questions.push(question);
renderQuestion(question);
}
function renderQuestion(question) {
const grid = document.getElementById('grid');
grid.innerHTML = '';
// Add grid cells
for (let i = 0; i < 9; i++) {
const cell = document.createElement('div');
if (i < question.gridStates.length) {
const canvas = document.createElement('canvas');
canvas.width = 50;
canvas.height = 50;
renderStateToCanvas(canvas, question.gridStates[i]);
cell.appendChild(canvas);
} else {
cell.textContent = '•••';
}
grid.appendChild(cell);
}
// Render options
const options = document.getElementById('options');
options.innerHTML = '';
question.options.forEach((state, index) => {
const btn = document.createElement('button');
btn.className = 'option-btn';
btn.innerHTML = '<canvas width="40" height="40"></canvas>';
// Render to button canvas
const canvas = btn.querySelector('canvas');
renderStateToCanvas(canvas, state);
btn.onclick = () => {
// Check answer
const isCorrect = statesEqual(state, question.correctState);
// Add class for visual feedback
btn.classList.add(isCorrect ? 'correct' : 'incorrect');
// Update count
if (isCorrect) correctCount++;
// Proceed to next question
setTimeout(() => {
currentQuestionIndex++;
if (currentQuestionIndex < totalQuestions) {
showQuestion();
} else {
showResults();
}
}, 500);
};
options.appendChild(btn);
});
}
function showResults() {
document.getElementById('test').style.display = 'none';
document.getElementById('results').style.display = 'block';
// Simple IQ estimation based on difficulty
let iq;
const percentage = correctCount / totalQuestions;
switch (difficulty) {
case 'easy': iq = 60 + 40 * percentage; break;
case 'medium': iq = 80 + 40 * percentage; break;
case 'hard': iq = 85 + 50 * percentage; break;
}
document.getElementById('iqScore').textContent = Math.round(iq);
}
function skipToResults() {
currentQuestionIndex++;
if (currentQuestionIndex < totalQuestions) {
showQuestion();
} else {
showResults();
}
}
</script>
</body>
</html>
```Here's a complete HTML implementation of an IQ test generator that meets your requirements. It creates 3×3 sequence-completion tasks with mathematically generated patterns, offers three difficulty levels, tracks responses, and provides an IQ estimate.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pattern IQ Test</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin: 20px 0;
}
.cell {
width: 100%;
aspect-ratio: 1;
border: 1px solid #ddd;
display: flex;
justify-content: center;
align-items: center;
background-color: white;
}
.options {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin: 20px 0;
}
.option {
aspect-ratio: 1;
border: 2px solid #ddd;
cursor: pointer;
transition: all 0.2s;
}
.option:hover {
border-color: #4CAF50;
}
.selected {
border-color: #4CAF50;
background-color: #e8f5e9;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 10px 2px;
cursor: pointer;
border-radius: 5px;
}
button:hover {
background-color: #45a049;
}
.difficulty-selector {
margin: 20px 0;
}
.result {
font-size: 18px;
font-weight: bold;
margin: 20px 0;
padding: 10px;
background-color: #e8f5e9;
border-radius: 5px;
display: none;
}
.progress {
margin: 20px 0;
height: 10px;
background-color: #e0e0e0;
border-radius: 5px;
}
.progress-bar {
height: 100%;
background-color: #4CAF50;
border-radius: 5px;
width: 0%;
transition: width 0.3s;
}
</style>
</head>
<body>
<div class="container">
<h1>Pattern IQ Test</h1>
<div class="difficulty-selector">
<h3>Select Difficulty:</h3>
<button id="easy">Easy</button>
<button id="medium">Medium</button>
<button id="hard">Hard</button>
</div>
<div id="test-area" style="display: none;">
<h3>Complete the pattern:</h3>
<div class="grid" id="pattern-grid"></div>
<h3>Select the correct option:</h3>
<div class="options" id="options-grid"></div>
<button id="submit">Submit Answer</button>
<button id="next" style="display: none;">Next Question</button>
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
</div>
<div class="result" id="result"></div>
</div>
<script>
// Configuration
const TEST_LENGTH = 10;
const DIFFICULTIES = {
easy: { complexity: 1, transformations: 1, patterns: 1 },
medium: { complexity: 2, transformations: 2, patterns: 2 },
hard: { complexity: 3, transformations: 3, patterns: 3 }
};
// State variables
let currentDifficulty = null;
let currentQuestion = 0;
let score = 0;
let questions = [];
let selectedOption = null;
// DOM elements
const patternGrid = document.getElementById('pattern-grid');
const optionsGrid = document.getElementById('options-grid');
const submitBtn = document.getElementById('submit');
const nextBtn = document.getElementById('next');
const resultDiv = document.getElementById('result');
const progressBar = document.getElementById('progress-bar');
const testArea = document.getElementById('test-area');
// Difficulty buttons
document.getElementById('easy').addEventListener('click', () => startTest('easy'));
document.getElementById('medium').addEventListener('click', () => startTest('medium'));
document.getElementById('hard').addEventListener('click', () => startTest('hard'));
// Test buttons
submitBtn.addEventListener('click', checkAnswer);
nextBtn.addEventListener('click', nextQuestion);
// Pattern generation functions
function generatePattern(difficulty) {
const config = DIFFICULTIES[difficulty];
const patternType = Math.floor(Math.random() * config.patterns) + 1;
// Base pattern (first cell)
const baseSize = 0.3 + Math.random() * 0.4;
const baseRotation = Math.floor(Math.random() * 4) * 90;
const baseColor = `hsl(${Math.floor(Math.random() * 360)}, 70%, 60%)`;
// Generate transformations
const transformations = [];
for (let i = 0; i < config.transformations; i++) {
transformations.push({
type: Math.floor(Math.random() * 4) + 1,
axis: Math.floor(Math.random() * 2),
amount: 0.1 + Math.random() * 0.3
});
}
// Generate the 3x3 pattern
const pattern = [];
for (let row = 0; row < 3; row++) {
for (let col = 0; col < 3; col++) {
if (row === 2 && col === 2) {
pattern.push(null); // This will be the missing piece
} else {
pattern.push(generateCell(row, col, baseSize, baseRotation, baseColor, transformations, patternType));
}
}
}
// Generate options (including the correct one)
const correctOption = generateCell(2, 2, baseSize, baseRotation, baseColor, transformations, patternType);
const options = [correctOption];
// Generate incorrect options
while (options.length < 6) {
// Vary one parameter to create plausible but incorrect options
const variationType = Math.floor(Math.random() * 4);
let newOption;
switch (variationType) {
case 0: // Different size
newOption = generateCell(2, 2, baseSize * (0.7 + Math.random() * 0.6), baseRotation, baseColor, transformations, patternType);
break;
case 1: // Different rotation
newOption = generateCell(2, 2, baseSize, (baseRotation + (Math.floor(Math.random() * 3) + 1) * 90) % 360, baseColor, transformations, patternType);
break;
case 2: // Different color
newOption = generateCell(2, 2, baseSize, baseRotation, `hsl(${(parseInt(baseColor.match(/\d+/)[0]) + 30 + Math.floor(Math.random() * 60)) % 360}, 70%, 60%)`, transformations, patternType);
break;
case 3: // Different transformation
const newTransformations = JSON.parse(JSON.stringify(transformations));
const changeIndex = Math.floor(Math.random() * newTransformations.length);
newTransformations[changeIndex].amount *= (0.5 + Math.random());
newOption = generateCell(2, 2, baseSize, baseRotation, baseColor, newTransformations, patternType);
break;
}
// Check if this option is sufficiently different from existing ones
if (!options.some(opt => arePatternsSimilar(opt, newOption))) {
options.push(newOption);
}
}
// Shuffle options
shuffleArray(options);
return {
pattern: pattern,
options: options,
correctIndex: options.indexOf(correctOption)
};
}
function generateCell(row, col, baseSize, baseRotation, baseColor, transformations, patternType) {
// Apply transformations based on position
let size = baseSize;
let rotation = baseRotation;
let color = baseColor;
transformations.forEach(trans => {
const positionValue = trans.axis === 0 ? row : col;
switch (trans.type) {
case 1: // Size change
size += trans.amount * positionValue;
break;
case 2: // Rotation change
rotation += trans.amount * 90 * positionValue;
break;
case 3: // Color hue change
const hue = parseInt(color.match(/\d+/)[0]);
color = `hsl(${(hue + trans.amount * 30 * positionValue) % 360}, 70%, 60%)`;
break;
case 4: // Pattern complexity change
// This affects how we draw the pattern later
break;
}
});
return {
size: Math.max(0.1, Math.min(0.9, size)),
rotation: rotation % 360,
color: color,
patternType: patternType,
transformations: transformations
};
}
function arePatternsSimilar(a, b) {
// Simple similarity check
const sizeDiff = Math.abs(a.size - b.size);
const rotationDiff = Math.min(Math.abs(a.rotation - b.rotation), 360 - Math.abs(a.rotation - b.rotation));
const colorDiff = Math.abs(parseInt(a.color.match(/\d+/)[0]) - parseInt(b.color.match(/\d+/)[0]));
return sizeDiff < 0.1 && rotationDiff < 30 && colorDiff < 30;
}
function drawPattern(cell, canvas) {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const size = Math.min(canvas.width, canvas.height) * cell.size;
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(cell.rotation * Math.PI / 180);
switch (cell.patternType) {
case 1: // Simple geometric shapes
ctx.fillStyle = cell.color;
ctx.beginPath();
ctx.arc(0, 0, size/2, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
break;
case 2: // More complex pattern
ctx.fillStyle = cell.color;
for (let i = 0; i < 4; i++) {
ctx.rotate(Math.PI/2);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(size/2, 0);
ctx.lineTo(size/2, size/2);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
break;
case 3: // Most complex pattern
ctx.fillStyle = cell.color;
const segments = 5;
for (let i = 0; i < segments; i++) {
const angle1 = (i * 2 * Math.PI) / segments;
const angle2 = ((i + 1) * 2 * Math.PI) / segments;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(Math.cos(angle1) * size/2, Math.sin(angle1) * size/2);
ctx.lineTo(Math.cos(angle2) * size/2, Math.sin(angle2) * size/2);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
break;
}
ctx.restore();
}
// Test flow functions
function startTest(difficulty) {
currentDifficulty = difficulty;
currentQuestion = 0;
score = 0;
questions = [];
// Generate all questions upfront
for (let i = 0; i < TEST_LENGTH; i++) {
questions.push(generatePattern(difficulty));
}
// Show test area
document.querySelector('.difficulty-selector').style.display = 'none';
testArea.style.display = 'block';
// Display first question
displayQuestion();
}
function displayQuestion() {
// Update progress
progressBar.style.width = `${(currentQuestion / TEST_LENGTH) * 100}%`;
// Clear previous
patternGrid.innerHTML = '';
optionsGrid.innerHTML = '';
selectedOption = null;
nextBtn.style.display = 'none';
submitBtn.style.display = 'inline-block';
const question = questions[currentQuestion];
// Display pattern
question.pattern.forEach((cell, index) => {
const cellDiv = document.createElement('div');
cellDiv.className = 'cell';
if (cell !== null) {
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
drawPattern(cell, canvas);
cellDiv.appendChild(canvas);
} else {
cellDiv.textContent = '?';
}
patternGrid.appendChild(cellDiv);
});
// Display options
question.options.forEach((option, index) => {
const optionDiv = document.createElement('div');
optionDiv.className = 'option';
optionDiv.dataset.index = index;
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
drawPattern(option, canvas);
optionDiv.appendChild(canvas);
optionDiv.addEventListener('click', () => {
// Deselect all
document.querySelectorAll('.option').forEach(opt => {
opt.classList.remove('selected');
});
// Select this one
optionDiv.classList.add('selected');
selectedOption = index;
});
optionsGrid.appendChild(optionDiv);
});
}
function checkAnswer() {
if (selectedOption === null) {
alert('Please select an answer');
return;
}
const question = questions[currentQuestion];
const isCorrect = selectedOption === question.correctIndex;
if (isCorrect) {
score++;
}
// Show feedback
document.querySelectorAll('.option').forEach((opt, index) => {
if (index === question.correctIndex) {
opt.style.borderColor = '#4CAF50';
opt.style.boxShadow = '0 0 5px #4CAF50';
} else if (index === selectedOption && !isCorrect) {
opt.style.borderColor = '#f44336';
opt.style.boxShadow = '0 0 5px #f44336';
}
});
// Prepare for next question
submitBtn.style.display = 'none';
nextBtn.style.display = 'inline-block';
}
function nextQuestion() {
currentQuestion++;
if (currentQuestion < TEST_LENGTH) {
displayQuestion();
} else {
showResults();
}
}
function showResults() {
testArea.style.display = 'none';
// Calculate IQ estimate based on score and difficulty
let iqEstimate;
const percentage = score / TEST_LENGTH;
if (currentDifficulty === 'easy') {
iqEstimate = 85 + percentage * 30; // 85-115
} else if (currentDifficulty === 'medium') {
iqEstimate = 100 + percentage * 30; // 100-130
} else {
iqEstimate = 115 + percentage * 30; // 115-145
}
resultDiv.textContent = `Your score: ${score}/${TEST_LENGTH}. Estimated IQ: ${Math.round(iqEstimate)}`;
resultDiv.style.display = 'block';
// Show difficulty selector again
document.querySelector('.difficulty-selector').style.display = 'block';
}
// Utility functions
function shuffleArray(array) {
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;
}
</script>
</body>
</html>
```
This implementation includes:
1. **Pattern Generation**:
- Creates mathematically consistent 3×3 patterns using transformations
- Supports three difficulty levels (easy, medium, hard)
- Generates multiple-choice options with one correct answer
2. **Visual Representation**:
- Draws patterns using HTML Canvas
- Shows the incomplete grid with a missing ninth cell
- Presents six options to choose from
3. **Test Flow**:
- Tracks user responses
- Provides feedback on correct/incorrect answers
- Shows progress through the test
- Calculates an IQ estimate based on performance and difficulty level
4. **User Interface**:
- Clean, responsive design
- Interactive elements with visual feedback
- Progress indicator
The patterns are generated using mathematical transformations (size, rotation, color changes) applied systematically across the grid, ensuring logical consistency. The difficulty levels control the complexity of these transformations.```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IQ Test Generator</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: #333;
}
.container {
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
h1 {
text-align: center;
color: #4a5568;
margin-bottom: 30px;
}
.controls {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 30px;
flex-wrap: wrap;
}
button {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
transition: all 0.3s ease;
}
.primary-btn {
background: #4299e1;
color: white;
}
.primary-btn:hover {
background: #3182ce;
}
.secondary-btn {
background: #e2e8f0;
color: #4a5568;
}
.secondary-btn:hover {
background: #cbd5e0;
}
select {
padding: 12px;
border-radius: 8px;
border: 2px solid #e2e8f0;
font-size: 16px;
}
.test-area {
display: none;
}
.question-info {
text-align: center;
margin-bottom: 20px;
font-size: 18px;
font-weight: bold;
}
.grid-container {
display: grid;
grid-template-columns: repeat(3, 120px);
gap: 10px;
justify-content: center;
margin-bottom: 30px;
}
.grid-cell {
width: 120px;
height: 120px;
border: 3px solid #4a5568;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
background: white;
}
.grid-cell.missing {
background: #f7fafc;
border-style: dashed;
}
.choices-container {
display: grid;
grid-template-columns: repeat(4, 120px);
gap: 15px;
justify-content: center;
margin-bottom: 30px;
}
.choice {
width: 120px;
height: 120px;
border: 3px solid #cbd5e0;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
background: white;
transition: all 0.3s ease;
}
.choice:hover {
border-color: #4299e1;
transform: scale(1.05);
}
.choice.selected {
border-color: #38a169;
background: #f0fff4;
}
.choice.correct {
border-color: #38a169;
background: #c6f6d5;
}
.choice.incorrect {
border-color: #e53e3e;
background: #fed7d7;
}
.results {
display: none;
text-align: center;
padding: 30px;
background: #f7fafc;
border-radius: 12px;
margin-top: 30px;
}
.iq-score {
font-size: 48px;
font-weight: bold;
color: #4299e1;
margin: 20px 0;
}
.progress {
margin-bottom: 20px;
text-align: center;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e2e8f0;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #4299e1;
transition: width 0.3s ease;
}
.timer {
text-align: center;
font-size: 20px;
font-weight: bold;
color: #e53e3e;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>🧠 IQ Test Generator</h1>
<div class="controls">
<select id="difficulty">
<option value="easy">Easy (Basic patterns)</option>
<option value="medium">Medium (Complex patterns)</option>
<option value="hard">Hard (Advanced logic)</option>
</select>
<button class="primary-btn" onclick="startTest()">Start Test</button>
<button class="secondary-btn" onclick="resetTest()">Reset</button>
</div>
<div class="test-area" id="testArea">
<div class="progress">
<div class="question-info">
Question <span id="currentQ">1</span> of <span id="totalQ">12</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
</div>
<div class="timer" id="timer">Time: 15:00</div>
<div class="grid-container" id="gridContainer"></div>
<div class="choices-container" id="choicesContainer"></div>
<button class="primary-btn" onclick="nextQuestion()" id="nextBtn" style="display: none; margin: 0 auto; display: block;">Next Question</button>
</div>
<div class="results" id="results">
<h2>Test Complete!</h2>
<div class="iq-score" id="iqScore">120</div>
<p>Your estimated IQ score</p>
<div id="analysis"></div>
<button class="primary-btn" onclick="resetTest()">Take Test Again</button>
</div>
</div>
<script>
class IQTestGenerator {
constructor() {
this.currentQuestion = 0;
this.totalQuestions = 12;
this.responses = [];
this.difficulty = 'easy';
this.startTime = null;
this.timeLimit = 15 * 60 * 1000; // 15 minutes
this.timer = null;
this.questions = [];
}
startTest() {
this.difficulty = document.getElementById('difficulty').value;
this.currentQuestion = 0;
this.responses = [];
this.startTime = Date.now();
this.generateAllQuestions();
document.querySelector('.controls').style.display = 'none';
document.getElementById('testArea').style.display = 'block';
document.getElementById('results').style.display = 'none';
this.startTimer();
this.displayQuestion();
}
generateAllQuestions() {
this.questions = [];
for (let i = 0; i < this.totalQuestions; i++) {
this.questions.push(this.generateQuestion());
}
}
generateQuestion() {
const transforms = this.getTransforms();
const pattern = this.selectPattern();
const grid = this.generateGrid(pattern, transforms);
const choices = this.generateChoices(grid, pattern, transforms);
return { grid, choices, correctAnswer: 0 };
}
getTransforms() {
const base = {
easy: [
{ type: 'color', values: ['#ff6b6b', '#4ecdc4', '#45b7d1'] },
{ type: 'size', values: [20, 40, 60] },
{ type: 'shape', values: ['circle', 'square', 'triangle'] }
],
medium: [
{ type: 'color', values: ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4'] },
{ type: 'size', values: [15, 30, 45, 60] },
{ type: 'rotation', values: [0, 90, 180, 270] },
{ type: 'shape', values: ['circle', 'square', 'triangle', 'diamond'] }
],
hard: [
{ type: 'color', values: ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57'] },
{ type: 'size', values: [12, 24, 36, 48, 60] },
{ type: 'rotation', values: [0, 45, 90, 135, 180, 225, 270, 315] },
{ type: 'count', values: [1, 2, 3] },
{ type: 'shape', values: ['circle', 'square', 'triangle', 'diamond', 'star'] }
]
};
return base[this.difficulty];
}
selectPattern() {
const patterns = [
'increment', 'cycle', 'alternate', 'matrix_add', 'diagonal'
];
return patterns[Math.floor(Math.random() * patterns.length)];
}
generateGrid(pattern, transforms) {
const grid = [];
const activeTransform = transforms[Math.floor(Math.random() * transforms.length)];
for (let i = 0; i < 9; i++) {
const row = Math.floor(i / 3);
const col = i % 3;
if (i === 8) { // Missing cell
grid.push(null);
continue;
}
const element = this.generateElement(i, row, col, pattern, activeTransform);
grid.push(element);
}
// Generate the correct answer for position 8
const correctElement = this.generateElement(8, 2, 2, pattern, activeTransform);
grid[8] = correctElement;
return grid;
}
generateElement(index, row, col, pattern, transform) {
let value;
switch (pattern) {
case 'increment':
value = transform.values[index % transform.values.length];
break;
case 'cycle':
value = transform.values[(row + col) % transform.values.length];
break;
case 'alternate':
value = transform.values[((row + col) % 2) * (transform.values.length - 1)];
break;
case 'matrix_add':
value = transform.values[(row + col) % transform.values.length];
break;
case 'diagonal':
if (row === col) {
value = transform.values[0];
} else if (row + col === 2) {
value = transform.values[1];
} else {
value = transform.values[2] || transform.values[0];
}
break;
default:
value = transform.values[index % transform.values.length];
}
return {
type: transform.type,
value: value,
shape: transform.type === 'shape' ? value : 'circle',
color: transform.type === 'color' ? value : '#45b7d1',
size: transform.type === 'size' ? value : 40,
rotation: transform.type === 'rotation' ? value : 0,
count: transform.type === 'count' ? value : 1
};
}
generateChoices(grid, pattern, transforms) {
const choices = [];
const correct = grid[8];
choices.push(correct);
// Generate 3 incorrect choices
for (let i = 0; i < 3; i++) {
const incorrect = this.generateIncorrectChoice(correct, transforms);
choices.push(incorrect);
}
// Shuffle choices but keep track of correct answer
const correctIndex = 0;
for (let i = choices.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[choices[i], choices[j]] = [choices[j], choices[i]];
if (i === correctIndex) {
this.questions[this.questions.length - 1].correctAnswer = j;
} else if (j === correctIndex) {
this.questions[this.questions.length - 1].correctAnswer = i;
}
}
return choices;
}
generateIncorrectChoice(correct, transforms) {
const incorrect = { ...correct };
const transform = transforms[Math.floor(Math.random() * transforms.length)];
const randomValue = transform.values[Math.floor(Math.random() * transform.values.length)];
switch (transform.type) {
case 'color':
incorrect.color = randomValue;
break;
case 'size':
incorrect.size = randomValue;
break;
case 'shape':
incorrect.shape = randomValue;
break;
case 'rotation':
incorrect.rotation = randomValue;
break;
case 'count':
incorrect.count = randomValue;
break;
}
return incorrect;
}
displayQuestion() {
const question = this.questions[this.currentQuestion];
document.getElementById('currentQ').textContent = this.currentQuestion + 1;
document.getElementById('totalQ').textContent = this.totalQuestions;
const progress = ((this.currentQuestion) / this.totalQuestions) * 100;
document.getElementById('progressFill').style.width = progress + '%';
this.renderGrid(question.grid);
this.renderChoices(question.choices);
document.getElementById('nextBtn').style.display = 'none';
}
renderGrid(grid) {
const container = document.getElementById('gridContainer');
container.innerHTML = '';
for (let i = 0; i < 9; i++) {
const cell = document.createElement('div');
cell.className = 'grid-cell';
if (i === 8) {
cell.className += ' missing';
cell.innerHTML = '?';
cell.style.fontSize = '48px';
cell.style.color = '#a0aec0';
} else {
const svg = this.createElement(grid[i]);
cell.appendChild(svg);
}
container.appendChild(cell);
}
}
renderChoices(choices) {
const container = document.getElementById('choicesContainer');
container.innerHTML = '';
choices.forEach((choice, index) => {
const choiceDiv = document.createElement('div');
choiceDiv.className = 'choice';
choiceDiv.onclick = () => this.selectChoice(index);
const svg = this.createElement(choice);
choiceDiv.appendChild(svg);
container.appendChild(choiceDiv);
});
}
createElement(element) {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', '100');
svg.setAttribute('height', '100');
const count = element.count || 1;
const spacing = 100 / (count + 1);
for (let i = 0; i < count; i++) {
const shape = this.createShape(element);
const x = spacing * (i + 1);
const y = 50;
shape.setAttribute('transform', `translate(${x}, ${y}) rotate(${element.rotation || 0})`);
svg.appendChild(shape);
}
return svg;
}
createShape(element) {
const shape = element.shape || 'circle';
const color = element.color || '#45b7d1';
const size = (element.size || 40) / 2;
let shapeElement;
switch (shape) {
case 'circle':
shapeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
shapeElement.setAttribute('r', size);
shapeElement.setAttribute('cx', 0);
shapeElement.setAttribute('cy', 0);
break;
case 'square':
shapeElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
shapeElement.setAttribute('width', size * 2);
shapeElement.setAttribute('height', size * 2);
shapeElement.setAttribute('x', -size);
shapeElement.setAttribute('y', -size);
break;
case 'triangle':
shapeElement = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
const points = `0,${-size} ${-size},${size} ${size},${size}`;
shapeElement.setAttribute('points', points);
break;
case 'diamond':
shapeElement = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
const diamondPoints = `0,${-size} ${size},0 0,${size} ${-size},0`;
shapeElement.setAttribute('points', diamondPoints);
break;
case 'star':
shapeElement = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
const starPoints = this.generateStarPoints(size);
shapeElement.setAttribute('points', starPoints);
break;
default:
shapeElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
shapeElement.setAttribute('r', size);
shapeElement.setAttribute('cx', 0);
shapeElement.setAttribute('cy', 0);
}
shapeElement.setAttribute('fill', color);
shapeElement.setAttribute('stroke', '#333');
shapeElement.setAttribute('stroke-width', '2');
return shapeElement;
}
generateStarPoints(size) {
const points = [];
const outerRadius = size;
const innerRadius = size * 0.4;
for (let i = 0; i < 10; i++) {
const angle = (i * Math.PI) / 5;
const radius = i % 2 === 0 ? outerRadius : innerRadius;
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius;
points.push(`${x},${y}`);
}
return points.join(' ');
}
selectChoice(index) {
const choices = document.querySelectorAll('.choice');
choices.forEach(choice => choice.classList.remove('selected'));
choices[index].classList.add('selected');
const question = this.questions[this.currentQuestion];
const isCorrect = index === question.correctAnswer;
this.responses.push({
questionIndex: this.currentQuestion,
selectedChoice: index,
correctChoice: question.correctAnswer,
isCorrect: isCorrect,
timeSpent: Date.now() - this.startTime
});
// Show correct/incorrect feedback
setTimeout(() => {
choices[question.correctAnswer].classList.add('correct');
if (!isCorrect) {
choices[index].classList.add('incorrect');
}
document.getElementById('nextBtn').style.display = 'block';
}, 500);
}
nextQuestion() {
this.currentQuestion++;
if (this.currentQuestion >= this.totalQuestions) {
this.finishTest();
} else {
this.displayQuestion();
}
}
startTimer() {
const updateTimer = () => {
const elapsed = Date.now() - this.startTime;
const remaining = Math.max(0, this.timeLimit - elapsed);
const minutes = Math.floor(remaining / 60000);
const seconds = Math.floor((remaining % 60000) / 1000);
document.getElementById('timer').textContent =
`Time: ${minutes}:${seconds.toString().padStart(2, '0')}`;
if (remaining <= 0) {
this.finishTest();
}
};
this.timer = setInterval(updateTimer, 1000);
updateTimer();
}
finishTest() {
clearInterval(this.timer);
const score = this.calculateIQ();
const analysis = this.generateAnalysis();
document.getElementById('testArea').style.display = 'none';
document.getElementById('results').style.display = 'block';
document.getElementById('iqScore').textContent = score;
document.getElementById('analysis').innerHTML = analysis;
}
calculateIQ() {
const correct = this.responses.filter(r => r.isCorrect).length;
const accuracy = correct / this.totalQuestions;
const totalTime = Date.now() - this.startTime;
const avgTimePerQuestion = totalTime / this.totalQuestions / 1000; // seconds
// Base IQ calculation
let baseIQ = 70 + (accuracy * 60);
// Difficulty multiplier
const difficultyMultiplier = {
easy: 1.0,
medium: 1.15,
hard: 1.3
};
baseIQ *= difficultyMultiplier[this.difficulty];
// Time bonus (faster = higher IQ, but cap the bonus)
const idealTimePerQuestion = 45; // seconds
if (avgTimePerQuestion < idealTimePerQuestion) {
const timeBonus = Math.min(10, (idealTimePerQuestion - avgTimePerQuestion) / 5);
baseIQ += timeBonus;
}
// Ensure realistic range
return Math.max(70, Math.min(160, Math.round(baseIQ)));
}
generateAnalysis() {
const correct = this.responses.filter(r => r.isCorrect).length;
const accuracy = (correct / this.totalQuestions * 100).toFixed(1);
const totalTime = Math.round((Date.now() - this.startTime) / 1000);
const avgTime = Math.round(totalTime / this.totalQuestions);
return `
<div style="text-align: left; max-width: 400px; margin: 0 auto;">
<h3>Performance Analysis:</h3>
<p><strong>Accuracy:</strong> ${accuracy}% (${correct}/${this.totalQuestions} correct)</p>
<p><strong>Total Time:</strong> ${Math.floor(totalTime/60)}:${(totalTime%60).toString().padStart(2,'0')}</p>
<p><strong>Average Time per Question:</strong> ${avgTime} seconds</p>
<p><strong>Difficulty Level:</strong> ${this.difficulty.charAt(0).toUpperCase() + this.difficulty.slice(1)}</p>
<hr>
<p><em>Note: This is an approximate estimate based on pattern recognition tasks.
Professional IQ tests include multiple cognitive domains and standardized conditions.</em></p>
</div>
`;
}
resetTest() {
clearInterval(this.timer);
this.currentQuestion = 0;
this.responses = [];
document.querySelector('.controls').style.display = 'flex';
document.getElementById('testArea').style.display = 'none';
document.getElementById('results').style.display = 'none';
}
}
const iqTest = new IQTestGenerator();
function startTest() {
iqTest.startTest();
}
function nextQuestion() {
iqTest.nextQuestion();
}
function resetTest() {
iqTest.resetTest();
}
</script>
</body>
</html>
```
This IQ test generator creates a comprehensive browser-based intelligence assessment tool with the following features:
## Key Features:
1. **Mathematical Transformation Functions**: Uses logical patterns like increment, cycle, alternate, matrix addition, and diagonal relationships to ensure consistent logic.
2. **Three Difficulty Levels**:
- **Easy**: Basic color, size, and shape patterns
- **Medium**: Adds rotation and more complex variations
- **Hard**: Includes element count, advanced rotations, and star shapes
3. **3×3 Grid Format**: Standard Raven's Progressive Matrices style with the 9th cell missing
4. **Multiple Choice Selection**: 4 choices per question with visual feedback
5. **Performance Tracking**:
- Accuracy percentage
- Response time per question
- Total time taken
- Difficulty adjustment in scoring
6. **IQ Estimation Algorithm**:
- Base score from accuracy (70-130 range)
- Difficulty multiplier (Easy: 1.0x, Medium: 1.15x, Hard: 1.3x)
- Time bonus for quick, accurate responses
- Final range capped between 70-160
7. **Visual Elements**:
- SVG-rendered shapes (circle, square, triangle, diamond, star)
- Color variations, size changes, rotations
- Multiple element counts
- Professional UI with progress tracking
The test generates 12 questions with a 15-minute time limit and provides detailed performance analysis along with an estimated IQ score based on pattern recognition abilities.# IQ Test Sequence Completion Task Generator
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sequence Completion IQ Test</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
h1, h2 {
color: #333;
}
.controls {
margin: 20px 0;
padding: 15px;
background-color: #f5f5f5;
border-radius: 5px;
}
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
margin: 20px 0;
}
.grid-cell {
aspect-ratio: 1;
border: 1px solid #ccc;
display: flex;
justify-content: center;
align-items: center;
background-color: #f9f9f9;
position: relative;
}
.grid-cell canvas {
width: 100%;
height: 100%;
display: block;
}
.missing {
background-color: #eee;
display: flex;
justify-content: center;
align-items: center;
}
.missing:after {
content: "?";
font-size: 2em;
color: #999;
}
.options-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 10px;
margin: 20px 0;
}
.option {
aspect-ratio: 1;
border: 1px solid #ddd;
cursor: pointer;
transition: all 0.2s;
}
.option:hover {
border-color: #666;
transform: scale(1.05);
}
.option.selected {
border: 3px solid #4CAF50;
transform: scale(1.05);
}
.results {
margin: 20px 0;
padding: 15px;
background-color: #e9f7ef;
border-radius: 5px;
display: none;
}
.progress {
margin-top: 10px;
font-weight: bold;
}
#progressBar {
height: 20px;
background-color: #ddd;
border-radius: 4px;
margin-top: 5px;
}
#progressBarFill {
height: 100%;
width: 0%;
background-color: #4CAF50;
border-radius: 4px;
transition: width 0.3s ease;
}
button {
padding: 10px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-right: 10px;
}
button:hover {
background-color: #45a049;
}
.explanation {
margin-top: 20px;
padding: 10px;
border-left: 4px solid #4CAF50;
background-color: #f9f9f9;
display: none;
}
</style>
</head>
<body>
<h1>Sequence Completion IQ Test</h1>
<div class="controls">
<p>Select difficulty level and press "Start Test" to begin.</p>
<div>
<input type="radio" id="easy" name="difficulty" value="easy" checked>
<label for="easy">Easy</label>
<input type="radio" id="medium" name="difficulty" value="medium">
<label for="medium">Medium</label>
<input type="radio" id="hard" name="difficulty" value="hard">
<label for="hard">Hard</label>
</div>
<div style="margin-top: 15px;">
<button id="startTest">Start Test</button>
<button id="showExplanation" style="display:none;">Show Explanation</button>
</div>
</div>
<div class="grid-container" id="grid">
<!-- Grid cells will be generated here -->
</div>
<div class="progress">
<div id="progressText">Question 0 of 0</div>
<div id="progressBar">
<div id="progressBarFill"></div>
</div>
</div>
<div class="options-container" id="options">
<!-- Options will be generated here -->
</div>
<div class="explanation" id="explanation"></div>
<div class="results" id="results">
<h2>Test Results</h2>
<p id="score"></p>
<p id="iqEstimate"></p>
<button id="restartTest">Try Another Test</button>
</div>
<script>
// Main variables
const NUM_QUESTIONS = 10;
const TOTAL_CELLS = 9;
let currentQuestion = 0;
let correctAnswers = 0;
let currentCorrectOption = 0;
let questionsData = [];
let testStartTime;
// DOM elements
const grid = document.getElementById('grid');
const optionsContainer = document.getElementById('options');
const startTestButton = document.getElementById('startTest');
const restartTestButton = document.getElementById('restartTest');
const resultsDiv = document.getElementById('results');
const progressText = document.getElementById('progressText');
const progressBarFill = document.getElementById('progressBarFill');
const showExplanationButton = document.getElementById('showExplanation');
const explanationDiv = document.getElementById('explanation');
// Transformation functions for generating patterns
const transformations = {
// Rotation functions
rotateClockwise: (x, y, canvasSize, shape, params) => {
const ctx = shape.getContext('2d');
ctx.translate(canvasSize/2, canvasSize/2);
ctx.rotate(params.angle * Math.PI / 180);
ctx.translate(-canvasSize/2, -canvasSize/2);
},
// Color functions
changeColor: (x, y, canvasSize, shape, params) => {
const ctx = shape.getContext('2d');
ctx.fillStyle = params.color;
},
// Size functions
scaleShape: (x, y, canvasSize, shape, params) => {
const ctx = shape.getContext('2d');
ctx.translate(canvasSize/2, canvasSize/2);
ctx.scale(params.scale, params.scale);
ctx.translate(-canvasSize/2, -canvasSize/2);
},
// Position functions
translateShape: (x, y, canvasSize, shape, params) => {
const ctx = shape.getContext('2d');
ctx.translate(params.dx, params.dy);
},
// Shape count
multipleShapes: (x, y, canvasSize, shape, params) => {
// Will be handled in the draw function
return params.count;
}
};
// Shape generators
const shapeGenerators = {
circle: (ctx, x, y, size, params) => {
ctx.beginPath();
ctx.arc(x, y, size/2, 0, 2 * Math.PI);
ctx.fill();
if (params.stroke) {
ctx.stroke();
}
},
square: (ctx, x, y, size, params) => {
ctx.beginPath();
ctx.rect(x - size/2, y - size/2, size, size);
ctx.fill();
if (params.stroke) {
ctx.stroke();
}
},
triangle: (ctx, x, y, size, params) => {
ctx.beginPath();
ctx.moveTo(x, y - size/2);
ctx.lineTo(x + size/2, y + size/2);
ctx.lineTo(x - size/2, y + size/2);
ctx.closePath();
ctx.fill();
if (params.stroke) {
ctx.stroke();
}
},
line: (ctx, x, y, size, params) => {
ctx.beginPath();
const angle = params.angle || 0;
const rad = angle * Math.PI / 180;
ctx.moveTo(x - Math.cos(rad) * size/2, y - Math.sin(rad) * size/2);
ctx.lineTo(x + Math.cos(rad) * size/2, y + Math.sin(rad) * size/2);
ctx.lineWidth = params.thickness || 3;
ctx.stroke();
}
};
// Generate a pattern based on row and column position
function generatePattern(row, col, difficulty) {
const baseShape = Math.floor(Math.random() * 4);
let shapes = ['circle', 'square', 'triangle', 'line'][baseShape];
// Base properties
let pattern = {
shapes: shapes,
color: '#000000',
size: 0.5,
x: 0.5,
y: 0.5,
angle: 0,
stroke: false,
count: 1
};
// Apply transformations based on difficulty and position
switch (difficulty) {
case 'easy':
// Simple row-based transformations
if (row === 0) pattern.size = 0.3;
else if (row === 1) pattern.size = 0.5;
else if (row === 2) pattern.size = 0.7;
if (col === 0) pattern.color = '#FF0000';
else if (col === 1) pattern.color = '#00FF00';
else if (col === 2) pattern.color = '#0000FF';
break;
case 'medium':
// More complex transformations
pattern.size = 0.3 + (row * 0.2);
if (col === 0) pattern.color = '#FF0000';
else if (col === 1) pattern.color = '#00FF00';
else if (col === 2) pattern.color = '#0000FF';
pattern.angle = row * 45 + col * 30;
if ((row + col) % 2 === 0) {
pattern.stroke = true;
}
break;
case 'hard':
// Advanced transformations
pattern.size = 0.25 + (row * 0.15) + (col * 0.1);
const hue = (row * 120 + col * 60) % 360;
pattern.color = `hsl(${hue}, 80%, 50%)`;
pattern.angle = row * 45 + col * 45;
pattern.x = 0.3 + col * 0.2;
pattern.y = 0.3 + row * 0.2;
pattern.count = 1 + (row + col) % 3;
if ((row * col) % 2 === 0) {
pattern.stroke = true;
}
break;
}
return pattern;
}
// Generate a consistent pattern set for all 9 cells
function generatePatternSet(difficulty) {
const patternSet = [];
// Function to check if two patterns are similar enough
const areTooSimilar = (p1, p2) => {
const sizeThreshold = 0.1;
const colorThreshold = 0.2;
// Very simple similarity check
return Math.abs(p1.size - p2.size) < sizeThreshold;
};
// Generate patterns for each position
for (let row = 0; row < 3; row++) {
for (let col = 0; col < 3; col++) {
let pattern = generatePattern(row, col, difficulty);
patternSet.push({ row, col, pattern });
}
}
return patternSet;
}
// Generate alternative options for the answer
function generateAlternativeOptions(correctPattern, difficulty) {
const options = [];
options.push(JSON.parse(JSON.stringify(correctPattern))); // Correct option
// Generate 3 incorrect options
for (let i = 0; i < 3; i++) {
let altPattern = JSON.parse(JSON.stringify(correctPattern));
// Modify the pattern based on difficulty
switch (difficulty) {
case 'easy':
// Change color or size
if (i % 2 === 0) {
const colors = ['#FF0000', '#00FF00', '#0000FF', '#FF00FF', '#FFFF00'];
altPattern.color = colors[Math.floor(Math.random() * colors.length)];
} else {
altPattern.size = correctPattern.size + (Math.random() * 0.4 - 0.2);
}
break;
case 'medium':
// Change two properties
altPattern.size = correctPattern.size + (Math.random() * 0.4 - 0.2);
altPattern.angle = correctPattern.angle + (Math.floor(Math.random() * 8) * 45);
if (i === 2) {
const shapes = ['circle', 'square', 'triangle', 'line'];
altPattern.shapes = shapes[Math.floor(Math.random() * shapes.length)];
}
break;
case 'hard':
// Change multiple properties
altPattern.size = correctPattern.size + (Math.random() * 0.4 - 0.2);
altPattern.angle = correctPattern.angle + (Math.floor(Math.random() * 8) * 45);
altPattern.x = correctPattern.x + (Math.random() * 0.4 - 0.2);
altPattern.y = correctPattern.y + (Math.random() * 0.4 - 0.2);
altPattern.count = Math.max(1, correctPattern.count + (Math.floor(Math.random() * 3) - 1));
if (i === 2) {
const shapes = ['circle', 'square', 'triangle', 'line'];
altPattern.shapes = shapes[Math.floor(Math.random() * shapes.length)];
}
break;
}
options.push(altPattern);
}
// Shuffle options
for (let i = options.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[options[i], options[j]] = [options[j], options[i]];
// Track the correct option
if (i === 0 && j === 0) currentCorrectOption = j;
else if (j === 0) currentCorrectOption = i;
}
return options;
}
// Draw a pattern on a canvas
function drawPattern(canvas, pattern) {
const ctx = canvas.getContext('2d');
const canvasSize = canvas.width;
// Clear canvas
ctx.clearRect(0, 0, canvasSize, canvasSize);
// Set up styling
ctx.fillStyle = pattern.color || '#000000';
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
const centerX = canvasSize * (pattern.x || 0.5);
const centerY = canvasSize * (pattern.y || 0.5);
const size = canvasSize * pattern.size;
// Handle multiple shapes
const count = pattern.count || 1;
const shapeType = pattern.shapes || 'circle';
ctx.save();
// Apply rotation if any
if (pattern.angle) {
ctx.translate(centerX, centerY);
ctx.rotate(pattern.angle * Math.PI / 180);
ctx.translate(-centerX, -centerY);
}
if (count === 1) {
// Draw a single shape
shapeGenerators[shapeType](ctx, centerX, centerY, size, {
stroke: pattern.stroke,
angle: pattern.angle
});
} else {
// Draw multiple shapes in a pattern
const radius = size / 2;
for (let i = 0; i < count; i++) {
const angle = (i * 2 * Math.PI) / count;
const x = centerX + Math.cos(angle) * radius;
const y = centerY + Math.sin(angle) * radius;
shapeGenerators[shapeType](ctx, x, y, size / count, {
stroke: pattern.stroke,
angle: pattern.angle
});
}
}
ctx.restore();
}
// Generate a question with a grid of patterns
function generateQuestion(difficulty) {
const patternSet = generatePatternSet(difficulty);
const missingPatternIndex = 8; // Last cell (bottom right)
const correctPattern = patternSet[missingPatternIndex].pattern;
const options = generateAlternativeOptions(correctPattern, difficulty);
// Generate explanation
let explanation = `This sequence follows a pattern where:<br>`;
if (difficulty === 'easy') {
explanation += `• Shapes get larger from top to bottom<br>`;
explanation += `• Colors change from red to green to blue from left to right`;
} else if (difficulty === 'medium') {
explanation += `• Shapes get larger from top to bottom<br>`;
explanation += `• Colors change from left to right<br>`;
explanation += `• Rotation increases by row and column position<br>`;
explanation += `• Some shapes have outlines based on position`;
} else {
explanation += `• Size changes based on both row and column position<br>`;
explanation += `• Colors follow a hue rotation pattern<br>`;
explanation += `• Rotation angles depend on position<br>`;
explanation += `• Position shifts slightly by row and column<br>`;
explanation += `• Number of shapes increases with sum of row and column<br>`;
explanation += `• Outlines appear in a checker pattern`;
}
return {
patterns: patternSet,
missingIndex: missingPatternIndex,
options: options,
correctOptionIndex: currentCorrectOption,
explanation: explanation
};
}
// Initialize the grid with the pattern
function initializeGrid(questionData) {
grid.innerHTML = '';
// Create the 3x3 grid
for (let i = 0; i < TOTAL_CELLS; i++) {
const cell = document.createElement('div');
cell.className = 'grid-cell';
// Make the missing cell empty
if (i === questionData.missingIndex) {
cell.classList.add('missing');
} else {
const canvas = document.createElement('canvas');
canvas.width = 150;
canvas.height = 150;
cell.appendChild(canvas);
// Find the relevant pattern
const row = Math.floor(i / 3);
const col = i % 3;
const patternData = questionData.patterns.find(p => p.row === row && p.col === col);
// Draw the pattern
drawPattern(canvas, patternData.pattern);
}
grid.appendChild(cell);
}
}
// Initialize the options
function initializeOptions(questionData) {
optionsContainer.innerHTML = '';
// Create the options
questionData.options.forEach((optionPattern, index) => {
const optionDiv = document.createElement('div');
optionDiv.className = 'option';
optionDiv.dataset.index = index;
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
optionDiv.appendChild(canvas);
// Draw the pattern
drawPattern(canvas, optionPattern);
// Add click event
optionDiv.addEventListener('click', () => {
selectOption(index, questionData.correctOptionIndex);
});
optionsContainer.appendChild(optionDiv);
});
}
// Select an option
function selectOption(selectedIndex, correctIndex) {
// Clear previous selections
document.querySelectorAll('.option').forEach(opt => {
opt.classList.remove('selected');
});
// Add selected class
document.querySelector(`.option[data-index="${selectedIndex}"]`).classList.add('selected');
// Record the answer
if (selectedIndex === correctIndex) {
correctAnswers++;
}
// Show explanation button
showExplanationButton.style.display = 'inline-block';
// Allow a small delay before moving to next question
setTimeout(() => {
moveToNextQuestion();
}, 1000);
}
// Move to the next question or finish the test
function moveToNextQuestion() {
currentQuestion++;
if (currentQuestion < NUM_QUESTIONS) {
// Update progress
updateProgress();
// Load next question
loadQuestion();
} else {
// Test is complete
finishTest();
}
}
// Update the progress indicators
function updateProgress() {
progressText.textContent = `Question ${currentQuestion} of ${NUM_QUESTIONS}`;
const percentage = (currentQuestion / NUM_QUESTIONS) * 100;
progressBarFill.style.width = `${percentage}%`;
}
// Load a question
function loadQuestion() {
// Hide the explanation
explanationDiv.style.display = 'none';
showExplanationButton.style.display = 'none';
// Get difficulty level
const difficultyRadios = document.getElementsByName('difficulty');
let difficulty = 'easy';
for (let radio of difficultyRadios) {
if (radio.checked) {
difficulty = radio.value;
break;
}
}
// Generate question data
const questionData = generateQuestion(difficulty);
questionsData[currentQuestion] = questionData;
// Set up the grid and options
initializeGrid(questionData);
initializeOptions(questionData);
}
// Calculate an IQ estimate based on performance
function calculateIQEstimate(correctCount, totalQuestions, difficulty, timeSpentMinutes) {
const baseIQ = 100;
const performanceRatio = correctCount / totalQuestions;
// Adjust for difficulty
let difficultyFactor = 1;
switch(difficulty) {
case 'easy': difficultyFactor = 0.9; break;
case 'medium': difficultyFactor = 1; break;
case 'hard': difficultyFactor = 1.15; break;
}
// Adjust for time (diminishing returns for very fast times)
const avgExpectedTimeMinutes = 1; // 1 minute per question
const timeRatio = Math.min(avgExpectedTimeMinutes * totalQuestions / timeSpentMinutes, 1.5);
// Calculate IQ adjustment
const performanceAdjustment = (performanceRatio - 0.5) * 50; // -25 to +25 range
const iqEstimate = Math.round(baseIQ + (performanceAdjustment * difficultyFactor * timeRatio));
// Cap the estimate within reasonable bounds
return Math.min(Math.max(iqEstimate, 70), 130);
}
// Finish the test and show results
function finishTest() {
const testEndTime = new Date();
const timeSpentMinutes = (testEndTime - testStartTime) / (1000 * 60);
// Get difficulty
const difficultyRadios = document.getElementsByName('difficulty');
let difficulty = 'easy';
for (let radio of difficultyRadios) {
if (radio.checked) {
difficulty = radio.value;
break;
}
}
// Calculate IQ estimate
const iqEstimate = calculateIQEstimate(correctAnswers, NUM_QUESTIONS, difficulty, timeSpentMinutes);
// Update results display
const scoreElement = document.getElementById('score');
scoreElement.textContent = `You answered ${correctAnswers} out of ${NUM_QUESTIONS} questions correctly.`;
const iqEstimateElement = document.getElementById('iqEstimate');
iqEstimateElement.textContent = `Based on your performance, your estimated IQ is approximately ${iqEstimate}.`;
// Show the results
resultsDiv.style.display = 'block';
// Hide the test
grid.style.display = 'none';
optionsContainer.style.display = 'none';
// Hide explanation elements
explanationDiv.style.display = 'none';
showExplanationButton.style.display = 'none';
}
// Start the test
startTestButton.addEventListener('click', () => {
// Reset test data
currentQuestion = 0;
correctAnswers = 0;
questionsData = [];
testStartTime = new Date();
// Set up UI
grid.style.display = 'grid';
optionsContainer.style.display = 'grid';
resultsDiv.style.display = 'none';
startTestButton.style.display = 'none';
// Update progress
updateProgress();
// Load first question
loadQuestion();
});
// Restart the test
restartTestButton.addEventListener('click', () => {
// Reset UI
grid.style.display = 'grid';
optionsContainer.style.display = 'grid';
resultsDiv.style.display = 'none';
startTestButton.style.display = 'inline-block';
// Show controls again
document.querySelector('.controls').style.display = 'block';
});
// Show explanation
showExplanationButton.addEventListener('click', () => {
if (currentQuestion > 0 && questionsData[currentQuestion - 1]) {
explanationDiv.innerHTML = questionsData[currentQuestion - 1].explanation;
explanationDiv.style.display = 'block';
}
});
</script>
</body>
</html>
```
This IQ test generator creates sequence-completion tasks where the user must identify the missing pattern in a 3×3 grid. Key features:
- Three difficulty levels (easy, medium, hard) with increasing complexity
- Mathematically-derived patterns ensuring logical consistency
- Multiple-choice selection for the missing pattern
- Progress tracking and scoring
- Interactive explanation of pattern logic
- IQ score estimation based on correct answers, difficulty, and time taken
- Fully client-side implementation with no external dependencies
The patterns are generated using transformations that follow logical rules based on position in the grid, with properties like size, color, rotation, and shape count changing consistently.```
<html>
<head>
<title>IQ Test Generator</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; }
#grid { display: grid; grid-template-columns: repeat(3, 100px); grid-gap: 5px; margin: 20px auto; }
.cell { width: 100px; height: 100px; border: 1px solid black; position: relative; }
canvas { width: 100%; height: 100%; }
button { margin: 10px; padding: 10px; }
#options { margin: 20px; }
.option { display: inline-block; margin: 5px; padding: 10px; border: 1px solid black; cursor: pointer; }
</style>
</head>
<body>
<h1>IQ Test Generator</h1>
<label for="difficulty">Select Difficulty: </label>
<select id="difficulty">
<option value="easy">Easy</option>
<option value="medium">Medium</option>
<option value="hard">Hard</option>
</select>
<button onclick="generateTest()">Start Test</button>
<div id="testArea">
<div id="grid"></div>
<div id="options"></div>
<button onclick="checkAnswer()" id="submitBtn" style="display:none;">Submit Answer</button>
<p id="feedback"></p>
<p>Score: <span id="score">0</span> / <span id="total">0</span></p>
<button onclick="endTest()" id="endBtn" style="display:none;">End Test</button>
<p id="iqResult"></p>
</div>
<script>
let score = 0;
let totalQuestions = 5; // Fixed for simplicity
let currentQuestion = 0;
let correctAnswer = null;
function generateTest() {
score = 0;
currentQuestion = 0;
document.getElementById('score').textContent = score;
document.getElementById('total').textContent = totalQuestions;
document.getElementById('feedback').textContent = '';
document.getElementById('iqResult').textContent = '';
generateQuestion();
}
function generateQuestion() {
const difficulty = document.getElementById('difficulty').value;
const grid = document.getElementById('grid');
grid.innerHTML = ''; // Clear grid
const optionsDiv = document.getElementById('options');
optionsDiv.innerHTML = ''; // Clear options
document.getElementById('submitBtn').style.display = 'none';
// Generate 3x3 grid data
const gridData = Array(3).fill().map(() => Array(3).fill(null));
let transformation;
if (difficulty === 'easy') {
// Simple: Increasing size circles
transformation = (x, y) => ({ type: 'circle', radius: 10 + (x + y) * 5 });
} else if (difficulty === 'medium') {
// Medium: Rotating squares
transformation = (x, y) => ({ type: 'square', size: 20, rotation: (x + y) * 45 });
} else {
// Hard: Combination: circles with increasing size and color change
transformation = (x, y) => ({ type: 'circle', radius: 10 + x * 5, color: `hsl(${y * 60}, 100%, 50%)` });
}
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (!(i === 2 && j === 2)) { // Leave the last one for options
gridData[i][j] = transformation(i, j);
}
}
}
// Render the grid except the last cell
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
const cell = document.createElement('div');
cell.className = 'cell';
if (gridData[i][j]) {
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
const ctx = canvas.getContext('2d');
drawShape(ctx, gridData[i][j]);
cell.appendChild(canvas);
}
grid.appendChild(cell);
}
}
// Generate options for the last cell
correctAnswer = transformation(2, 2); // The correct one
const options = [correctAnswer];
while (options.length < 4) { // 4 options total
const wrong = generateWrongOption(transformation);
if (!options.some(opt => JSON.stringify(opt) === JSON.stringify(wrong))) {
options.push(wrong);
}
}
options.sort(() => Math.random() - 0.5); // Shuffle
options.forEach((opt, index) => {
const optionDiv = document.createElement('div');
optionDiv.className = 'option';
optionDiv.innerHTML = `Option ${index + 1}`;
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
const ctx = canvas.getContext('2d');
drawShape(ctx, opt);
optionDiv.appendChild(canvas);
optionDiv.onclick = () => selectOption(index, opt);
optionsDiv.appendChild(optionDiv);
});
document.getElementById('submitBtn').style.display = 'block'; // Show submit button
}
function drawShape(ctx, shape) {
ctx.clearRect(0, 0, 100, 100);
if (shape.type === 'circle') {
ctx.beginPath();
ctx.arc(50, 50, shape.radius, 0, 2 * Math.PI);
if (shape.color) ctx.fillStyle = shape.color;
ctx.fill();
} else if (shape.type === 'square') {
ctx.save();
ctx.translate(50, 50); // Center
ctx.rotate(shape.rotation * Math.PI / 180);
ctx.fillStyle = 'blue';
ctx.fillRect(-shape.size / 2, -shape.size / 2, shape.size, shape.size);
ctx.restore();
}
}
function generateWrongOption(transformation) {
const difficulty = document.getElementById('difficulty').value;
if (difficulty === 'easy') {
return { type: 'circle', radius: transformation(2, 2).radius + 5 }; // Slightly off
} else if (difficulty === 'medium') {
return { type: 'square', size: transformation(2, 2).size, rotation: transformation(2, 2).rotation + 45 };
} else {
return { type: 'circle', radius: transformation(2, 2).radius - 5, color: 'red' }; // Altered
}
}
function selectOption(index, option) {
// For simplicity, just store the selected one when submitting
sessionStorage.setItem('selectedOption', JSON.stringify(option));
}
function checkAnswer() {
const selected = JSON.parse(sessionStorage.getItem('selectedOption'));
if (JSON.stringify(selected) === JSON.stringify(correctAnswer)) {
score++;
document.getElementById('feedback').textContent = 'Correct!';
document.getElementById('score').textContent = score;
} else {
document.getElementById('feedback').textContent = 'Incorrect!';
}
currentQuestion++;
if (currentQuestion < totalQuestions) {
setTimeout(generateQuestion, 1000); // Next question
} else {
endTest();
}
}
function endTest() {
const iqEstimate = 100 + (score / totalQuestions) * 50; // Arbitrary formula
document.getElementById('iqResult').textContent = `Test Complete! Your approximate IQ estimate: ${Math.round(iqEstimate)}`;
document.getElementById('endBtn').style.display = 'none'; // Hide if needed
}
</script>
</body>
</html>
```