๐ Realistic 3D Cricket Game
by SuperWolf111819 lines66.2 KB๐ ๏ธ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>๐ Realistic 3D Cricket Game</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
body {
overflow: hidden;
background: #0a0a1a;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
canvas {
display: block;
}
/* HUD overlay */
#hud {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
/* Scoreboard */
#scoreboard {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.85);
border: 2px solid rgba(255, 215, 0, 0.4);
border-radius: 16px;
padding: 14px 28px;
display: flex;
align-items: center;
gap: 30px;
backdrop-filter: blur(12px);
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.7);
pointer-events: auto;
color: #fff;
font-size: 15px;
min-width: 480px;
justify-content: center;
flex-wrap: wrap;
}
#scoreboard .team {
display: flex;
align-items: center;
gap: 10px;
}
#scoreboard .team .name {
font-weight: 700;
font-size: 18px;
letter-spacing: 0.5px;
color: #f0f0f0;
}
#scoreboard .team .score {
font-weight: 800;
font-size: 28px;
color: #ffd700;
min-width: 60px;
text-align: center;
}
#scoreboard .team .wickets {
font-size: 18px;
color: #ff6b6b;
}
#scoreboard .team .overs {
font-size: 14px;
color: #aaa;
margin-left: 4px;
}
#scoreboard .vs {
color: #666;
font-weight: 300;
font-size: 16px;
}
#scoreboard .status {
color: #7bed9f;
font-size: 13px;
font-weight: 600;
background: rgba(123, 237, 159, 0.15);
padding: 4px 16px;
border-radius: 20px;
border: 1px solid rgba(123, 237, 159, 0.3);
}
#scoreboard .batting-info {
color: #bbb;
font-size: 13px;
display: flex;
gap: 16px;
align-items: center;
}
#scoreboard .batting-info .batsman {
color: #7bed9f;
font-weight: 600;
}
#scoreboard .batting-info .bowler {
color: #ffd93d;
font-weight: 600;
}
/* Control buttons */
#controls {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 18px;
pointer-events: auto;
flex-wrap: wrap;
justify-content: center;
background: rgba(0, 0, 0, 0.7);
padding: 14px 24px;
border-radius: 50px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.08);
}
#controls button {
padding: 10px 22px;
border: none;
border-radius: 30px;
font-size: 14px;
font-weight: 700;
cursor: pointer;
transition: all 0.2s ease;
pointer-events: auto;
text-transform: uppercase;
letter-spacing: 0.5px;
background: rgba(255, 255, 255, 0.08);
color: #ccc;
border: 1px solid rgba(255, 255, 255, 0.06);
}
#controls button:hover {
transform: scale(1.05);
background: rgba(255, 255, 255, 0.18);
color: #fff;
}
#controls button.primary {
background: #ffd700;
color: #1a1a2e;
border-color: #ffd700;
}
#controls button.primary:hover {
background: #ffe44d;
box-shadow: 0 0 30px rgba(255, 215, 0, 0.3);
}
#controls button.danger {
background: #ff6b6b;
color: #1a1a2e;
border-color: #ff6b6b;
}
#controls button.danger:hover {
background: #ff8787;
box-shadow: 0 0 30px rgba(255, 107, 107, 0.3);
}
#controls button.green {
background: #2ecc71;
color: #1a1a2e;
border-color: #2ecc71;
}
#controls button.green:hover {
background: #55e68a;
box-shadow: 0 0 30px rgba(46, 204, 113, 0.3);
}
#controls button:disabled {
opacity: 0.3;
cursor: not-allowed;
transform: none !important;
}
#controls .key-hint {
color: #666;
font-size: 10px;
font-weight: 400;
display: block;
text-align: center;
margin-top: 2px;
}
/* Power meter */
#power-meter {
position: absolute;
right: 30px;
top: 50%;
transform: translateY(-50%);
width: 24px;
height: 240px;
background: rgba(0, 0, 0, 0.7);
border-radius: 20px;
border: 2px solid rgba(255, 215, 0, 0.3);
overflow: hidden;
pointer-events: none;
backdrop-filter: blur(4px);
opacity: 0;
transition: opacity 0.3s ease;
}
#power-meter.active {
opacity: 1;
}
#power-meter .fill {
position: absolute;
bottom: 0;
width: 100%;
background: linear-gradient(to top, #2ecc71, #ffd700, #ff6b6b);
height: 0%;
transition: height 0.05s linear;
border-radius: 20px;
}
#power-meter .label {
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
color: #fff;
font-size: 11px;
font-weight: 600;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.8);
white-space: nowrap;
}
/* Ball indicator */
#ball-indicator {
position: absolute;
bottom: 120px;
left: 50%;
transform: translateX(-50%);
color: #fff;
font-size: 14px;
background: rgba(0, 0, 0, 0.6);
padding: 6px 20px;
border-radius: 20px;
backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.06);
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
text-align: center;
font-weight: 500;
}
#ball-indicator.active {
opacity: 1;
}
/* Camera selector */
#camera-selector {
position: absolute;
top: 100px;
right: 20px;
display: flex;
flex-direction: column;
gap: 8px;
pointer-events: auto;
}
#camera-selector button {
padding: 6px 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
background: rgba(0, 0, 0, 0.6);
color: #aaa;
font-size: 11px;
cursor: pointer;
transition: all 0.2s ease;
backdrop-filter: blur(4px);
font-weight: 600;
letter-spacing: 0.3px;
}
#camera-selector button:hover {
background: rgba(255, 215, 0, 0.2);
color: #fff;
border-color: rgba(255, 215, 0, 0.3);
}
#camera-selector button.active {
background: rgba(255, 215, 0, 0.25);
color: #ffd700;
border-color: #ffd700;
}
/* Match info */
#match-info {
position: absolute;
top: 95px;
left: 20px;
color: #aaa;
font-size: 12px;
background: rgba(0, 0, 0, 0.6);
padding: 10px 16px;
border-radius: 12px;
backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.04);
pointer-events: none;
line-height: 1.6;
}
#match-info .highlight {
color: #ffd700;
font-weight: 600;
}
/* Message popup */
#message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 48px;
font-weight: 800;
text-shadow: 0 4px 40px rgba(0, 0, 0, 0.9);
pointer-events: none;
opacity: 0;
transition: opacity 0.5s ease;
text-align: center;
background: rgba(0, 0, 0, 0.5);
padding: 30px 50px;
border-radius: 24px;
backdrop-filter: blur(8px);
border: 2px solid rgba(255, 215, 0, 0.1);
}
#message.active {
opacity: 1;
}
#message .sub {
font-size: 20px;
font-weight: 400;
color: #ccc;
margin-top: 8px;
}
@media (max-width: 800px) {
#scoreboard {
min-width: auto;
width: 92%;
padding: 10px 16px;
gap: 12px;
font-size: 12px;
top: 10px;
}
#scoreboard .team .score {
font-size: 22px;
min-width: 40px;
}
#scoreboard .team .name {
font-size: 14px;
}
#controls {
bottom: 16px;
padding: 10px 14px;
gap: 10px;
border-radius: 30px;
width: 95%;
flex-wrap: wrap;
}
#controls button {
padding: 6px 14px;
font-size: 11px;
}
#power-meter {
right: 12px;
height: 160px;
width: 18px;
}
#camera-selector {
top: 80px;
right: 10px;
}
#camera-selector button {
padding: 4px 8px;
font-size: 9px;
}
#match-info {
display: none;
}
#message {
font-size: 28px;
padding: 20px 30px;
}
#message .sub {
font-size: 16px;
}
#ball-indicator {
bottom: 80px;
font-size: 12px;
padding: 4px 14px;
}
}
</style>
</head>
<body>
<!-- HUD -->
<div id="hud">
<!-- Scoreboard -->
<div id="scoreboard">
<div class="team">
<span class="name" id="team1-name">INDIA</span>
<span class="score" id="team1-score">0</span>
<span class="wickets" id="team1-wickets">/0</span>
<span class="overs" id="team1-overs">(0.0)</span>
</div>
<span class="vs">vs</span>
<div class="team">
<span class="name" id="team2-name">AUS</span>
<span class="score" id="team2-score">0</span>
<span class="wickets" id="team2-wickets">/0</span>
<span class="overs" id="team2-overs">(0.0)</span>
</div>
<span class="status" id="match-status">โ Live</span>
<div class="batting-info">
<span><span class="batsman" id="current-batsman">Kohli</span> โฆ</span>
<span><span class="bowler" id="current-bowler">Starc</span> โก</span>
</div>
</div>
<!-- Match info -->
<div id="match-info">
<div>Match: <span class="highlight" id="match-type">T20</span></div>
<div>Overs: <span class="highlight" id="match-overs">0.0</span></div>
<div>Runs: <span class="highlight" id="match-runs">0</span></div>
<div>Wickets: <span class="highlight" id="match-wickets">0</span></div>
<div>Balls: <span class="highlight" id="match-balls">0</span></div>
</div>
<!-- Power meter -->
<div id="power-meter">
<div class="label">POWER</div>
<div class="fill" id="power-fill" style="height:0%"></div>
</div>
<!-- Ball indicator -->
<div id="ball-indicator">๐ Ready to bowl</div>
<!-- Message -->
<div id="message">
<span id="msg-text">๐ SIX!</span>
<div class="sub" id="msg-sub">What a shot!</div>
</div>
<!-- Camera selector -->
<div id="camera-selector">
<button data-cam="broadcast" class="active">๐บ Broadcast</button>
<button data-cam="bowler">๐ฏ Bowler</button>
<button data-cam="batsman">๐ Batsman</button>
<button data-cam="behind">๐ Behind Stumps</button>
<button data-cam="field">๐ฟ Field</button>
<button data-cam="ball">โช Ball Follow</button>
</div>
<!-- Controls -->
<div id="controls">
<button class="primary" id="btn-bowl">โก Bowl</button>
<button class="green" id="btn-shot">๐ Shot</button>
<button id="btn-defend">๐ก๏ธ Defend</button>
<button id="btn-leave">๐ถ Leave</button>
<button class="danger" id="btn-reset">๐ Reset</button>
<button id="btn-toss">๐ช Toss</button>
</div>
</div>
<!-- Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js">
</script>
<script>
// ============================================================
// CRICKET GAME โ Full 3D Simulation
// ============================================================
// -------- GLOBALS --------
const TEAMS = {
INDIA: { name: 'INDIA', color: 0x1a5c9e, players: ['Kohli', 'Sharma', 'Rahul', 'Pandya', 'Jadeja', 'Bumrah'] },
AUS: { name: 'AUS', color: 0xf7c93e, players: ['Smith', 'Warner', 'Labuschagne', 'Maxwell', 'Starc', 'Cummins'] },
ENG: { name: 'ENG', color: 0x1a3d7c, players: ['Root', 'Buttler', 'Stokes', 'Bairstow', 'Rashid', 'Archer'] },
PAK: { name: 'PAK', color: 0x1a6b1a, players: ['Babar', 'Rizwan', 'Shaheen', 'Hasan', 'Imad', 'Shadab'] },
NZ: { name: 'NZ', color: 0x1a1a1a, players: ['Kane', 'Conway', 'Boult', 'Southee', 'Santner', 'Latham'] },
SA: { name: 'SA', color: 0x1a7a3a, players: ['De Kock', 'Bavuma', 'Rabada', 'Nortje', 'Maharaj', 'Miller'] },
};
const TEAM_NAMES = Object.keys(TEAMS);
// Game state
const state = {
team1: 'INDIA',
team2: 'AUS',
batting: 'INDIA',
bowling: 'AUS',
runs: 0,
wickets: 0,
overs: 0,
balls: 0,
totalRuns: 0,
totalWickets: 0,
totalOvers: 0,
innings: 1,
maxOvers: 20,
matchType: 'T20',
isBowling: false,
isBatting: false,
isRunning: false,
ballInPlay: false,
deliveryType: 'fast',
shotType: 'drive',
power: 0.5,
isCharging: false,
chargeStart: 0,
targetRuns: 0,
matchOver: false,
inningsBreak: false,
batsman: 0,
bowler: 0,
striker: 'Kohli',
nonStriker: 'Sharma',
currentBowler: 'Starc',
fielders: [],
deliveries: 0,
extras: { wide: 0, noBall: 0, bye: 0, legBye: 0 },
boundary: 0,
sixes: 0,
dots: 0,
};
// -------- THREE.JS SETUP --------
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a2a4a);
scene.fog = new THREE.Fog(0x1a2a4a, 120, 250);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 500);
camera.position.set(40, 30, 60);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
document.body.prepend(renderer.domElement);
// -------- LIGHTS --------
const ambient = new THREE.AmbientLight(0x334466, 0.4);
scene.add(ambient);
const sun = new THREE.DirectionalLight(0xffeedd, 1.8);
sun.position.set(40, 60, 30);
sun.castShadow = true;
sun.shadow.mapSize.width = 2048;
sun.shadow.mapSize.height = 2048;
sun.shadow.camera.near = 0.5;
sun.shadow.camera.far = 150;
sun.shadow.camera.left = -80;
sun.shadow.camera.right = 80;
sun.shadow.camera.top = 80;
sun.shadow.camera.bottom = -80;
scene.add(sun);
const fill = new THREE.DirectionalLight(0x8888ff, 0.3);
fill.position.set(-30, 20, -40);
scene.add(fill);
const rim = new THREE.DirectionalLight(0xffaa44, 0.2);
rim.position.set(-20, 10, 50);
scene.add(rim);
// Stadium lights
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI * 2;
const x = Math.cos(angle) * 75;
const z = Math.sin(angle) * 75;
const light = new THREE.PointLight(0xffeedd, 0.15, 100);
light.position.set(x, 45, z);
scene.add(light);
}
// -------- GROUND / STADIUM --------
function createStadium() {
const group = new THREE.Group();
// Ground plane
const groundGeo = new THREE.PlaneGeometry(180, 180);
const groundMat = new THREE.MeshStandardMaterial({
color: 0x2d5a1e,
roughness: 0.9,
metalness: 0.0
});
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.05;
ground.receiveShadow = true;
group.add(ground);
// Pitch (strip)
const pitchMat = new THREE.MeshStandardMaterial({ color: 0x8a7a5a, roughness: 0.8 });
const pitch = new THREE.Mesh(new THREE.PlaneGeometry(4, 22), pitchMat);
pitch.rotation.x = -Math.PI / 2;
pitch.position.set(0, 0.01, 0);
pitch.receiveShadow = true;
group.add(pitch);
// Pitch markings
const markMat = new THREE.MeshStandardMaterial({ color: 0xffffff, transparent: true, opacity: 0.15 });
for (let i = -5; i <= 5; i++) {
const mark = new THREE.Mesh(new THREE.PlaneGeometry(0.02, 0.5), markMat);
mark.rotation.x = -Math.PI / 2;
mark.position.set(0, 0.02, i * 2);
group.add(mark);
}
// Boundary rope
const ropePoints = [];
const segments = 64;
for (let i = 0; i <= segments; i++) {
const theta = (i / segments) * Math.PI * 2;
const r = 28;
ropePoints.push(new THREE.Vector3(Math.cos(theta) * r, 0.08, Math.sin(theta) * r));
}
const ropeGeo = new THREE.BufferGeometry().setFromPoints(ropePoints);
const ropeMat = new THREE.LineBasicMaterial({ color: 0xff4444, linewidth: 2 });
const rope = new THREE.Line(ropeGeo, ropeMat);
group.add(rope);
// Boundary flags
for (let i = 0; i < 12; i++) {
const theta = (i / 12) * Math.PI * 2;
const r = 28;
const flag = new THREE.Mesh(
new THREE.CylinderGeometry(0.06, 0.06, 0.8, 4),
new THREE.MeshStandardMaterial({ color: 0xff4444 })
);
flag.position.set(Math.cos(theta) * r, 0.4, Math.sin(theta) * r);
flag.castShadow = true;
group.add(flag);
const top = new THREE.Mesh(
new THREE.SphereGeometry(0.08, 6, 6),
new THREE.MeshStandardMaterial({ color: 0xffdd44 })
);
top.position.set(Math.cos(theta) * r, 0.85, Math.sin(theta) * r);
group.add(top);
}
// Stumps (two ends)
const stumpMat = new THREE.MeshStandardMaterial({ color: 0xd4a574, roughness: 0.6 });
const stumpPositions = [
[0, 0, -11],
[0, 0, 11]
];
stumpPositions.forEach((pos) => {
for (let i = -1; i <= 1; i++) {
const stump = new THREE.Mesh(
new THREE.CylinderGeometry(0.04, 0.06, 0.8, 8),
stumpMat
);
stump.position.set(pos[0] + i * 0.08, 0.4, pos[2]);
stump.castShadow = true;
group.add(stump);
// Bail
if (i < 1) {
const bail = new THREE.Mesh(
new THREE.CylinderGeometry(0.015, 0.015, 0.2, 4),
new THREE.MeshStandardMaterial({ color: 0xffaa44 })
);
bail.rotation.z = Math.PI / 2;
bail.position.set(pos[0] + i * 0.08 + 0.1, 0.82, pos[2]);
group.add(bail);
}
}
});
// Crease lines
const creaseMat = new THREE.MeshStandardMaterial({ color: 0xffffff, transparent: true, opacity: 0.3 });
for (let z of [-11, 11]) {
const crease = new THREE.Mesh(new THREE.PlaneGeometry(0.8, 0.02), creaseMat);
crease.rotation.x = -Math.PI / 2;
crease.position.set(0, 0.02, z);
group.add(crease);
}
// Stadium stands (simplified)
const standMat = new THREE.MeshStandardMaterial({ color: 0x2a2a3a, roughness: 0.9 });
const roofMat = new THREE.MeshStandardMaterial({ color: 0x3a3a5a, roughness: 0.7, metalness: 0.1 });
for (let i = 0; i < 16; i++) {
const theta = (i / 16) * Math.PI * 2;
const r1 = 32 + Math.sin(i * 1.7) * 3;
const r2 = 45 + Math.sin(i * 1.7 + 0.5) * 4;
const r3 = 52 + Math.sin(i * 1.7 + 1) * 3;
// Stand section
const shape = new THREE.Shape();
shape.moveTo(Math.cos(theta) * r1, Math.sin(theta) * r1);
shape.lineTo(Math.cos(theta + 0.12) * r1, Math.sin(theta + 0.12) * r1);
shape.lineTo(Math.cos(theta + 0.14) * r2, Math.sin(theta + 0.14) * r2);
shape.lineTo(Math.cos(theta + 0.02) * r2, Math.sin(theta + 0.02) * r2);
shape.lineTo(Math.cos(theta) * r1, Math.sin(theta) * r1);
const extSettings = { depth: 3 + Math.sin(i * 2) * 1.5, bevelEnabled: false };
const standGeo = new THREE.ExtrudeGeometry(shape, extSettings);
const standMesh = new THREE.Mesh(standGeo, standMat);
standMesh.position.y = 0;
standMesh.rotation.y = 0;
standMesh.castShadow = true;
standMesh.receiveShadow = true;
group.add(standMesh);
// Roof
const roofShape = new THREE.Shape();
const rr1 = r2 + 1;
const rr2 = r3 + 2;
roofShape.moveTo(Math.cos(theta) * rr1, Math.sin(theta) * rr1);
roofShape.lineTo(Math.cos(theta + 0.1) * rr1, Math.sin(theta + 0.1) * rr1);
roofShape.lineTo(Math.cos(theta + 0.12) * rr2, Math.sin(theta + 0.12) * rr2);
roofShape.lineTo(Math.cos(theta + 0.02) * rr2, Math.sin(theta + 0.02) * rr2);
roofShape.lineTo(Math.cos(theta) * rr1, Math.sin(theta) * rr1);
const roofGeo2 = new THREE.ExtrudeGeometry(roofShape, { depth: 0.5, bevelEnabled: false });
const roofMesh = new THREE.Mesh(roofGeo2, roofMat);
roofMesh.position.y = 3.5 + Math.sin(i * 1.3) * 0.5;
roofMesh.castShadow = true;
roofMesh.receiveShadow = true;
group.add(roofMesh);
}
// Scoreboard (digital display)
const boardGroup = new THREE.Group();
const boardMat = new THREE.MeshStandardMaterial({ color: 0x111122, roughness: 0.3, metalness: 0.6 });
const board = new THREE.Mesh(new THREE.BoxGeometry(3, 1.8, 0.3), boardMat);
board.position.set(-20, 6, 25);
board.castShadow = true;
group.add(board);
// Screen glow
const screenMat = new THREE.MeshStandardMaterial({
color: 0x00ff88,
emissive: 0x00ff88,
emissiveIntensity: 0.15,
transparent: true,
opacity: 0.6
});
const screen = new THREE.Mesh(new THREE.PlaneGeometry(2.6, 1.4), screenMat);
screen.position.set(-20, 6, 25.16);
group.add(screen);
return group;
}
const stadium = createStadium();
scene.add(stadium);
// -------- PLAYER CREATION --------
function createPlayer(teamColor, isBatsman = false, isBowler = false) {
const group = new THREE.Group();
// Body (torso)
const bodyMat = new THREE.MeshStandardMaterial({
color: teamColor,
roughness: 0.5,
metalness: 0.05
});
const body = new THREE.Mesh(new THREE.CylinderGeometry(0.4, 0.35, 0.9, 8), bodyMat);
body.position.y = 0.95;
body.castShadow = true;
group.add(body);
// Head
const headMat = new THREE.MeshStandardMaterial({
color: 0xf5d0b8,
roughness: 0.6
});
const head = new THREE.Mesh(new THREE.SphereGeometry(0.22, 8, 8), headMat);
head.position.y = 1.55;
head.castShadow = true;
group.add(head);
// Hair (simplified)
const hairMat = new THREE.MeshStandardMaterial({ color: 0x2a1a0a, roughness: 0.8 });
const hair = new THREE.Mesh(new THREE.SphereGeometry(0.23, 8, 8, 0, Math.PI * 2, 0, Math.PI / 2), hairMat);
hair.position.y = 1.63;
hair.scale.set(1, 0.6, 1);
group.add(hair);
// Left arm
const armMat = new THREE.MeshStandardMaterial({ color: 0xf5d0b8, roughness: 0.5 });
const lArm = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.07, 0.5, 6), armMat);
lArm.position.set(-0.45, 1.2, 0);
lArm.rotation.z = 0.3;
lArm.castShadow = true;
group.add(lArm);
// Right arm
const rArm = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.07, 0.5, 6), armMat);
rArm.position.set(0.45, 1.2, 0);
rArm.rotation.z = -0.3;
rArm.castShadow = true;
group.add(rArm);
// Legs
const legMat = new THREE.MeshStandardMaterial({
color: 0x2a2a3a,
roughness: 0.7
});
const lLeg = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.1, 0.5, 6), legMat);
lLeg.position.set(-0.15, 0.25, 0);
lLeg.castShadow = true;
group.add(lLeg);
const rLeg = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.1, 0.5, 6), legMat);
rLeg.position.set(0.15, 0.25, 0);
rLeg.castShadow = true;
group.add(rLeg);
// Shoes
const shoeMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.9 });
const lShoe = new THREE.Mesh(new THREE.SphereGeometry(0.08, 6, 6), shoeMat);
lShoe.position.set(-0.15, 0.02, 0.08);
group.add(lShoe);
const rShoe = new THREE.Mesh(new THREE.SphereGeometry(0.08, 6, 6), shoeMat);
rShoe.position.set(0.15, 0.02, 0.08);
group.add(rShoe);
// Bat (for batsman)
if (isBatsman) {
const batMat = new THREE.MeshStandardMaterial({ color: 0xd4a574, roughness: 0.5 });
const handleMat = new THREE.MeshStandardMaterial({ color: 0x1a1a2a, roughness: 0.7 });
const blade = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.35, 0.015), batMat);
blade.position.set(0.55, 1.0, 0.2);
blade.rotation.z = -0.2;
blade.rotation.x = -0.3;
group.add(blade);
const handle = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 0.2, 6), handleMat);
handle.position.set(0.55, 1.22, 0.2);
handle.rotation.z = -0.2;
handle.rotation.x = -0.3;
group.add(handle);
// Helmet
const helmMat = new THREE.MeshStandardMaterial({ color: 0xcccccc, metalness: 0.3, roughness: 0.3 });
const helm = new THREE.Mesh(new THREE.SphereGeometry(0.24, 8, 8, 0, Math.PI * 2, 0, Math.PI / 2.5), helmMat);
helm.position.y = 1.6;
helm.scale.set(1, 0.5, 1);
group.add(helm);
// Helmet grill
const grillMat = new THREE.MeshStandardMaterial({ color: 0x333333, metalness: 0.5 });
const grill = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.06, 0.02), grillMat);
grill.position.set(0, 1.48, 0.2);
group.add(grill);
}
// Gloves (for wicketkeeper/batsman)
if (isBatsman) {
const gloveMat = new THREE.MeshStandardMaterial({ color: 0xcc3333, roughness: 0.6 });
const g1 = new THREE.Mesh(new THREE.SphereGeometry(0.065, 6, 6), gloveMat);
g1.position.set(0.5, 1.35, 0.15);
group.add(g1);
const g2 = new THREE.Mesh(new THREE.SphereGeometry(0.065, 6, 6), gloveMat);
g2.position.set(-0.5, 1.35, 0.15);
group.add(g2);
}
// Pads (for batsman/wk)
if (isBatsman) {
const padMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.7 });
const p1 = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.3, 0.04), padMat);
p1.position.set(-0.15, 0.5, 0.12);
group.add(p1);
const p2 = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.3, 0.04), padMat);
p2.position.set(0.15, 0.5, 0.12);
group.add(p2);
}
group.userData = { body, head, lArm, rArm, lLeg, rLeg };
return group;
}
// -------- CREATE TEAMS --------
const teamPlayers = {};
const playerPositions = [];
function setupTeams() {
const team1Color = TEAMS[state.team1].color;
const team2Color = TEAMS[state.team2].color;
// Fielding team (team2) - place around the field
const fieldPositions = [
[-12, 0, -8],
[-10, 0, -5],
[-8, 0, -2],
[8, 0, -2],
[10, 0, -5],
[12, 0, -8],
[-6, 0, 8],
[6, 0, 8],
[-14, 0, 2],
[14, 0, 2],
[0, 0, 14]
];
// Clear old players
const toRemove = [];
scene.children.forEach(child => {
if (child.userData && child.userData.isPlayer) toRemove.push(child);
});
toRemove.forEach(child => scene.remove(child));
// Create batsman (team1)
const batsman = createPlayer(team1Color, true, false);
batsman.position.set(0.3, 0, -10.5);
batsman.rotation.y = 0;
batsman.userData.isPlayer = true;
batsman.userData.role = 'batsman';
batsman.userData.team = state.team1;
scene.add(batsman);
state.batsmanObj = batsman;
// Non-striker
const nonStriker = createPlayer(team1Color, true, false);
nonStriker.position.set(-1.2, 0, 9.5);
nonStriker.rotation.y = Math.PI;
nonStriker.userData.isPlayer = true;
nonStriker.userData.role = 'nonstriker';
nonStriker.userData.team = state.team1;
scene.add(nonStriker);
state.nonStrikerObj = nonStriker;
// Bowler (team2)
const bowler = createPlayer(team2Color, false, true);
bowler.position.set(0, 0, 12.5);
bowler.rotation.y = Math.PI;
bowler.userData.isPlayer = true;
bowler.userData.role = 'bowler';
bowler.userData.team = state.team2;
scene.add(bowler);
state.bowlerObj = bowler;
// Wicketkeeper (team2)
const wk = createPlayer(team2Color, true, false);
wk.position.set(0.3, 0, -10.8);
wk.rotation.y = 0;
wk.userData.isPlayer = true;
wk.userData.role = 'wicketkeeper';
wk.userData.team = state.team2;
scene.add(wk);
state.wkObj = wk;
// Fielders (team2)
state.fielderObjs = [];
fieldPositions.forEach((pos, i) => {
const f = createPlayer(team2Color, false, false);
f.position.set(pos[0], 0, pos[1]);
f.rotation.y = Math.atan2(-pos[0], -pos[1]);
f.userData.isPlayer = true;
f.userData.role = 'fielder';
f.userData.team = state.team2;
f.userData.homePos = new THREE.Vector3(pos[0], 0, pos[1]);
f.userData.speed = 0.5 + Math.random() * 0.3;
scene.add(f);
state.fielderObjs.push(f);
});
// Update UI
document.getElementById('team1-name').textContent = state.team1;
document.getElementById('team2-name').textContent = state.team2;
}
// -------- BALL --------
const ballMat = new THREE.MeshStandardMaterial({
color: 0xcc3333,
roughness: 0.5,
metalness: 0.1
});
const ball = new THREE.Mesh(new THREE.SphereGeometry(0.055, 16, 16), ballMat);
ball.position.set(0, 0.5, 13);
ball.castShadow = true;
ball.userData.velocity = new THREE.Vector3(0, 0, 0);
ball.userData.trail = [];
scene.add(ball);
// Ball seam
const seamMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.3 });
const seam = new THREE.Mesh(new THREE.TorusGeometry(0.055, 0.008, 8, 16), seamMat);
seam.rotation.x = Math.PI / 2;
seam.position.y = 0;
ball.add(seam);
// -------- PHYSICS --------
const GRAVITY = -9.8;
const BOUNCE_COEFF = 0.55;
const FRICTION = 0.97;
const AIR_RESISTANCE = 0.999;
let ballInPlay = false;
let ballTrajectory = [];
let deliveryData = {
speed: 0,
swing: 0,
seam: 0,
spin: 0,
type: 'fast',
targetX: 0,
targetZ: -11,
releasePoint: new THREE.Vector3(0, 2.2, 12.5)
};
function resetBall() {
ball.position.set(0, 0.5, 13);
ball.userData.velocity.set(0, 0, 0);
ballInPlay = false;
ball.userData.trail = [];
document.getElementById('ball-indicator').classList.remove('active');
document.getElementById('ball-indicator').textContent = '๐ Ready to bowl';
}
function bowlBall(targetX, targetZ, speed, swing, spin, type) {
if (ballInPlay || state.matchOver) return;
const from = new THREE.Vector3(0, 2.2, 12.5);
const to = new THREE.Vector3(targetX, 0.3, targetZ);
const dir = new THREE.Vector3().copy(to).sub(from);
const dist = dir.length();
dir.normalize();
// Add swing (horizontal curve)
const swingDir = new THREE.Vector3(swing, 0, 0);
const vel = dir.clone().multiplyScalar(speed);
vel.y += 0.8; // upward toss
vel.x += swing * 0.3;
ball.position.copy(from);
ball.userData.velocity.copy(vel);
ball.userData.swing = swing;
ball.userData.spin = spin;
ball.userData.type = type;
ball.userData.releaseTime = Date.now();
ball.userData.bounceCount = 0;
ball.userData.seamMovement = (Math.random() - 0.5) * 0.15;
ballInPlay = true;
state.ballInPlay = true;
state.deliveries++;
state.balls++;
document.getElementById('ball-indicator').classList.add('active');
document.getElementById('ball-indicator').textContent = `โก ${type} delivery!`;
// Update bowler animation
animateBowlerRunup();
}
function updateBallPhysics(delta) {
if (!ballInPlay) return;
const vel = ball.userData.velocity;
const pos = ball.position;
// Gravity
vel.y += GRAVITY * delta;
// Air resistance
vel.multiplyScalar(AIR_RESISTANCE);
// Swing (drift in air)
if (ball.userData.swing && pos.y > 0.3) {
vel.x += ball.userData.swing * delta * 0.5;
}
// Seam movement (random deviation on bounce)
if (ball.userData.bounceCount === 0 && pos.y < 0.2 && vel.y < 0) {
vel.x += ball.userData.seamMovement * 1.5;
vel.z += (Math.random() - 0.5) * 0.1;
ball.userData.bounceCount++;
}
// Spin effect after bounce
if (ball.userData.spin && pos.y < 0.3) {
const spinFactor = ball.userData.spin * delta * 2;
vel.x += spinFactor * (pos.z > 0 ? -1 : 1);
vel.z += spinFactor * 0.3;
}
// Update position
pos.x += vel.x * delta;
pos.y += vel.y * delta;
pos.z += vel.z * delta;
// Bounce off ground
if (pos.y < 0.05) {
pos.y = 0.05;
if (vel.y < 0) {
vel.y *= -BOUNCE_COEFF;
ball.userData.bounceCount++;
// Reduce speed on bounce
vel.x *= 0.9;
vel.z *= 0.9;
}
}
// Boundary check
const radius = Math.sqrt(pos.x * pos.x + pos.z * pos.z);
if (radius > 27) {
// Boundary!
if (pos.y > 0.5) {
// Six!
handleBoundary(true);
} else {
// Four!
handleBoundary(false);
}
ballInPlay = false;
state.ballInPlay = false;
resetBall();
return;
}
// Check if ball passed the stumps
if (pos.z < -11.5 && ball.userData.bounceCount > 0) {
// Check if batsman hit it
const batPos = state.batsmanObj.position;
const dx = pos.x - batPos.x;
const dz = pos.z - batPos.z;
const dist2 = Math.sqrt(dx * dx + dz * dz);
if (dist2 < 0.8 && pos.y > 0.3 && pos.y < 1.8) {
// Hit!
handleBatContact(pos);
} else if (pos.y < 0.4) {
// Ball passed through - check LBW or bowled
if (Math.abs(pos.x) < 0.3) {
// Bowled!
handleWicket('bowled');
ballInPlay = false;
state.ballInPlay = false;
resetBall();
return;
}
// Byes
handleByes();
ballInPlay = false;
state.ballInPlay = false;
resetBall();
return;
}
}
// Ball goes too far
if (pos.y < -2 || Math.abs(pos.x) > 40 || Math.abs(pos.z) > 40) {
ballInPlay = false;
state.ballInPlay = false;
resetBall();
}
// Trail
if (ballInPlay) {
ball.userData.trail.push(pos.clone());
if (ball.userData.trail.length > 30) ball.userData.trail.shift();
}
// Update ball rotation
ball.rotation.x += vel.z * delta * 5;
ball.rotation.z += vel.x * delta * 5;
}
// -------- BATTING --------
let batSwing = 0;
let isBatting = false;
let shotResult = null;
function handleBatContact(ballPos) {
if (isBatting) return;
isBatting = true;
const power = state.power;
const shotType = state.shotType;
// Determine direction based on shot type
let dx = 0,
dz = -1;
switch (shotType) {
case 'drive':
dx = (ballPos.x - state.batsmanObj.position.x) * 0.5;
dz = -1.2;
break;
case 'cover':
dx = -0.8;
dz = -1.2;
break;
case 'pull':
dx = 0.6;
dz = -1.5;
break;
case 'sweep':
dx = -0.3;
dz = -1.8;
break;
case 'defend':
dx = 0;
dz = -0.3;
break;
default:
dx = 0;
dz = -1;
}
const speed = 8 + power * 15;
const angle = Math.atan2(dx, dz);
// Edge chance
const edgeChance = 0.15 - power * 0.08;
if (Math.random() < edgeChance) {
// Edge!
const edgeDir = new THREE.Vector3(
(Math.random() - 0.5) * 2,
0.3 + Math.random() * 0.5,
-0.5 - Math.random() * 0.5
).normalize();
ball.userData.velocity.copy(edgeDir.multiplyScalar(speed * 0.6));
// Check for catch
if (Math.random() < 0.4) {
handleWicket('caught');
showMessage('๐ฏ Caught!', 'Edged to the keeper!');
} else {
// Runs from edge
const runs = Math.floor(Math.random() * 2) + 1;
addRuns(runs);
showMessage(`โ๏ธ ${runs} runs`, 'Edge!');
}
isBatting = false;
return;
}
// Normal hit
const hitDir = new THREE.Vector3(
Math.sin(angle) * 0.8 + (Math.random() - 0.5) * 0.2,
0.3 + power * 0.6 + Math.random() * 0.2,
-Math.cos(angle) * 0.8 - 0.3
).normalize();
const hitSpeed = speed * (0.7 + power * 0.3);
ball.userData.velocity.copy(hitDir.multiplyScalar(hitSpeed));
ball.userData.velocity.y += 1 + power * 1.5;
// Check for boundary
const isSix = power > 0.7 && Math.random() < 0.3;
if (isSix) {
setTimeout(() => {
handleBoundary(true);
isBatting = false;
}, 500);
} else if (power > 0.5 && Math.random() < 0.2) {
setTimeout(() => {
handleBoundary(false);
isBatting = false;
}, 500);
} else {
// Running
const runs = Math.floor(Math.random() * 2) + 1;
if (Math.random() < 0.1 && runs === 2) {
// Run out chance
if (Math.random() < 0.3) {
handleWicket('runout');
showMessage('โ Run Out!', 'Direct hit!');
} else {
addRuns(runs);
showMessage(`๐ ${runs} runs`, 'Quick running!');
}
} else {
addRuns(runs);
showMessage(`๐ ${runs} run${runs > 1 ? 's' : ''}`, 'Good running');
}
isBatting = false;
}
// Bat swing animation
batSwing = 1.0;
setTimeout(() => { isBatting = false; }, 800);
// Update ball state
ballInPlay = false;
state.ballInPlay = false;
}
function handleBoundary(isSix) {
if (isSix) {
addRuns(6);
state.sixes++;
showMessage('๐ SIX!', 'Maximum!');
} else {
addRuns(4);
state.boundary++;
showMessage('๐ FOUR!', 'Races to the boundary!');
}
ballInPlay = false;
state.ballInPlay = false;
resetBall();
}
function handleByes() {
const byes = Math.floor(Math.random() * 2) + 1;
addRuns(byes);
state.extras.bye += byes;
showMessage(`๐ ${byes} bye${byes > 1 ? 's' : ''}`, 'Missed the ball');
}
function handleWicket(type) {
state.wickets++;
if (type === 'bowled') {
showMessage('๐ฅ Bowled!', 'Clean bowled!');
} else if (type === 'caught') {
showMessage('๐๏ธ Caught!', 'Out!');
} else if (type === 'lbw') {
showMessage('๐ LBW!', 'Plumb in front!');
} else if (type === 'runout') {
showMessage('โ Run Out!', 'Direct hit!');
} else if (type === 'stumped') {
showMessage('๐งค Stumped!', 'Quick hands!');
}
if (state.wickets >= 10) {
endInnings();
}
updateScoreboard();
setTimeout(resetBall, 1500);
}
function addRuns(runs) {
state.runs += runs;
state.totalRuns += runs;
updateScoreboard();
}
function endInnings() {
state.matchOver = true;
state.inningsBreak = true;
showMessage('๐ Innings Over!', `${state.team1} scored ${state.runs}/${state.wickets}`);
document.getElementById('match-status').textContent = 'โธ๏ธ Innings Break';
document.getElementById('btn-bowl').disabled = true;
document.getElementById('btn-shot').disabled = true;
}
// -------- BOWLING --------
let isBowlingAction = false;
let bowlPower = 0;
let isCharging = false;
let chargeStartTime = 0;
function animateBowlerRunup() {
const bowler = state.bowlerObj;
if (!bowler) return;
// Simple run-up animation
const startZ = 12.5;
const endZ = 10.5;
const duration = 300;
let startTime = Date.now();
function runupStep() {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
const ease = 1 - Math.pow(1 - progress, 3);
bowler.position.z = startZ - (startZ - endZ) * ease;
bowler.position.y = Math.sin(progress * Math.PI) * 0.15;
if (progress < 1) {
requestAnimationFrame(runupStep);
} else {
// Release ball
bowler.position.z = endZ;
bowler.position.y = 0;
// Trigger ball release
releaseBall();
}
}
runupStep();
}
function releaseBall() {
// Determine delivery type
const types = ['fast', 'swing', 'seam', 'offspin', 'legspin'];
const type = types[Math.floor(Math.random() * types.length)];
let speed = 0,
swing = 0,
spin = 0,
targetX = 0;
switch (type) {
case 'fast':
speed = 22 + Math.random() * 4;
swing = (Math.random() - 0.5) * 0.3;
targetX = (Math.random() - 0.5) * 0.6;
break;
case 'swing':
speed = 18 + Math.random() * 3;
swing = (Math.random() - 0.5) * 0.8;
targetX = (Math.random() - 0.5) * 0.8;
break;
case 'seam':
speed = 20 + Math.random() * 3;
swing = (Math.random() - 0.5) * 0.2;
targetX = (Math.random() - 0.5) * 0.4;
break;
case 'offspin':
speed = 14 + Math.random() * 3;
spin = 0.5 + Math.random() * 0.5;
targetX = (Math.random() - 0.5) * 0.3;
break;
case 'legspin':
speed = 13 + Math.random() * 3;
spin = -0.5 - Math.random() * 0.5;
targetX = (Math.random() - 0.5) * 0.3;
break;
default:
speed = 18;
targetX = 0;
}
// Add some variation
targetX += (Math.random() - 0.5) * 0.4;
const targetZ = -11;
state.deliveryType = type;
state.power = 0.5 + Math.random() * 0.4;
bowlBall(targetX, targetZ, speed, swing, spin, type);
// Update UI
document.getElementById('ball-indicator').textContent = `๐ฏ ${type.toUpperCase()} delivery!`;
}
// -------- FIELDING AI --------
function updateFielders() {
if (!ballInPlay) {
// Return to positions
state.fielderObjs.forEach(f => {
const home = f.userData.homePos;
f.position.x += (home.x - f.position.x) * 0.02;
f.position.z += (home.z - f.position.z) * 0.02;
});
return;
}
const ballPos = ball.position;
state.fielderObjs.forEach(f => {
const dx = ballPos.x - f.position.x;
const dz = ballPos.z - f.position.z;
const dist = Math.sqrt(dx * dx + dz * dz);
if (dist < 10 && dist > 0.5) {
const speed = f.userData.speed * 3;
f.position.x += (dx / dist) * speed * 0.02;
f.position.z += (dz / dist) * speed * 0.02;
// Face ball
f.rotation.y = Math.atan2(dx, dz);
}
// Dive if close to ball
if (dist < 0.6 && ballPos.y < 0.5) {
// Fielding!
if (ballPos.y < 0.3) {
// Stop ball
ball.userData.velocity.multiplyScalar(0.3);
// Throw to keeper
setTimeout(() => {
const keeper = state.wkObj;
if (keeper) {
const kdx = keeper.position.x - f.position.x;
const kdz = keeper.position.z - f.position.z;
const kdist = Math.sqrt(kdx * kdx + kdz * kdz);
if (kdist < 15) {
// Throw animation
const throwSpeed = 10;
ball.userData.velocity.set(
(kdx / kdist) * throwSpeed,
1 + Math.random(),
(kdz / kdist) * throwSpeed
);
}
}
}, 100);
}
}
});
}
// -------- CAMERA --------
let currentCamera = 'broadcast';
const cameraTarget = new THREE.Vector3(0, 0, 0);
function updateCamera() {
const pos = camera.position;
const target = cameraTarget;
switch (currentCamera) {
case 'broadcast':
pos.set(40, 28, 55);
target.set(0, 1, -2);
break;
case 'bowler':
pos.set(0, 3, 18);
target.set(0, 0.5, -8);
break;
case 'batsman':
pos.set(-2, 2, -12);
target.set(0, 1, 0);
break;
case 'behind':
pos.set(0, 1.5, -14);
target.set(0, 0.5, 0);
break;
case 'field':
pos.set(25, 18, 10);
target.set(0, 0.5, -2);
break;
case 'ball':
if (ballInPlay) {
const bp = ball.position;
pos.set(bp.x + 5, bp.y + 4, bp.z + 8);
target.copy(bp);
} else {
pos.set(20, 15, 30);
target.set(0, 0.5, -2);
}
break;
default:
pos.set(40, 28, 55);
target.set(0, 1, -2);
}
camera.lookAt(target);
}
// -------- UI UPDATES --------
function updateScoreboard() {
document.getElementById('team1-score').textContent = state.runs;
document.getElementById('team1-wickets').textContent = `/${state.wickets}`;
document.getElementById('team1-overs').textContent = `(${state.overs.toFixed(1)})`;
// Update batsman/bowler names
const batsmen = TEAMS[state.team1].players;
const bowlers = TEAMS[state.team2].players;
state.striker = batsmen[state.batsman % batsmen.length];
state.currentBowler = bowlers[state.bowler % bowlers.length];
document.getElementById('current-batsman').textContent = state.striker;
document.getElementById('current-bowler').textContent = state.currentBowler;
document.getElementById('match-overs').textContent = state.overs.toFixed(1);
document.getElementById('match-runs').textContent = state.runs;
document.getElementById('match-wickets').textContent = state.wickets;
document.getElementById('match-balls').textContent = state.balls;
}
function showMessage(main, sub = '') {
const el = document.getElementById('message');
document.getElementById('msg-text').textContent = main;
document.getElementById('msg-sub').textContent = sub;
el.classList.add('active');
clearTimeout(el._timeout);
el._timeout = setTimeout(() => {
el.classList.remove('active');
}, 2500);
}
// -------- GAME LOOP --------
let lastTime = 0;
let bowlerTimer = 0;
function gameLoop(time) {
const delta = Math.min((time - lastTime) / 1000, 0.05);
lastTime = time;
// Update ball physics
updateBallPhysics(delta);
// Update fielders
updateFielders();
// Update camera
updateCamera();
// Update bowler timer
if (!ballInPlay && !state.matchOver && !state.inningsBreak) {
bowlerTimer += delta;
if (bowlerTimer > 2.5) {
bowlerTimer = 0;
// Auto-bowl if not in batting mode
if (!state.isBatting) {
// Random delivery
const types = ['fast', 'swing', 'seam', 'offspin', 'legspin'];
const type = types[Math.floor(Math.random() * types.length)];
state.deliveryType = type;
const targetX = (Math.random() - 0.5) * 0.8;
const speed = 16 + Math.random() * 8;
const swing = (Math.random() - 0.5) * 0.6;
const spin = (Math.random() - 0.5) * 0.8;
bowlBall(targetX, -11, speed, swing, spin, type);
document.getElementById('ball-indicator').textContent = `๐ฏ ${type.toUpperCase()}!`;
}
}
}
// Update batsman animation
if (state.batsmanObj) {
const b = state.batsmanObj;
if (batSwing > 0) {
b.rotation.x = -0.3 + batSwing * 0.8;
batSwing *= 0.95;
} else {
b.rotation.x = -0.1;
}
// Slight idle sway
b.position.y = Math.sin(time * 0.001) * 0.008;
}
// Update bowler animation
if (state.bowlerObj) {
const b = state.bowlerObj;
b.position.y = Math.sin(time * 0.0015) * 0.01;
}
renderer.render(scene, camera);
requestAnimationFrame(gameLoop);
}
// -------- CONTROLS --------
document.getElementById('btn-bowl').addEventListener('click', () => {
if (ballInPlay || state.matchOver) return;
const types = ['fast', 'swing', 'seam', 'offspin', 'legspin'];
const type = types[Math.floor(Math.random() * types.length)];
state.deliveryType = type;
const targetX = (Math.random() - 0.5) * 0.8;
const speed = 16 + Math.random() * 8;
const swing = (Math.random() - 0.5) * 0.6;
const spin = (Math.random() - 0.5) * 0.8;
bowlBall(targetX, -11, speed, swing, spin, type);
document.getElementById('ball-indicator').textContent = `๐ฏ ${type.toUpperCase()}!`;
});
document.getElementById('btn-shot').addEventListener('click', () => {
if (!ballInPlay || isBatting) return;
const shots = ['drive', 'cover', 'pull', 'sweep'];
state.shotType = shots[Math.floor(Math.random() * shots.length)];
state.power = 0.5 + Math.random() * 0.5;
// Trigger shot on next ball contact
// The ball physics will handle it
showMessage('๐ Shot!', `Playing ${state.shotType}`);
});
document.getElementById('btn-defend').addEventListener('click', () => {
if (!ballInPlay || isBatting) return;
state.shotType = 'defend';
state.power = 0.2 + Math.random() * 0.2;
showMessage('๐ก๏ธ Defend', 'Playing safe');
});
document.getElementById('btn-leave').addEventListener('click', () => {
if (!ballInPlay) return;
// Leave the ball
ball.userData.velocity.multiplyScalar(0.5);
showMessage('๐ถ Leave', 'Let it go');
});
document.getElementById('btn-reset').addEventListener('click', () => {
resetBall();
state.runs = 0;
state.wickets = 0;
state.overs = 0;
state.balls = 0;
state.totalRuns = 0;
state.totalWickets = 0;
state.matchOver = false;
state.inningsBreak = false;
state.deliveries = 0;
state.boundary = 0;
state.sixes = 0;
state.extras = { wide: 0, noBall: 0, bye: 0, legBye: 0 };
document.getElementById('btn-bowl').disabled = false;
document.getElementById('btn-shot').disabled = false;
document.getElementById('match-status').textContent = 'โ Live';
document.getElementById('match-type').textContent = 'T20';
updateScoreboard();
showMessage('๐ Match Reset', 'New innings');
setupTeams();
});
document.getElementById('btn-toss').addEventListener('click', () => {
const winner = Math.random() < 0.5 ? state.team1 : state.team2;
const choice = Math.random() < 0.5 ? 'bat' : 'bowl';
showMessage(`๐ช ${winner} won the toss`, `Elect to ${choice} first`);
if (winner === state.team1) {
state.batting = state.team1;
state.bowling = state.team2;
} else {
state.batting = state.team2;
state.bowling = state.team1;
}
});
// Camera switching
document.querySelectorAll('#camera-selector button').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#camera-selector button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentCamera = btn.dataset.cam;
// Smooth transition
camera.position.lerp(new THREE.Vector3(40, 28, 55), 0.5);
});
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'b' || e.key === 'B') {
document.getElementById('btn-bowl').click();
}
if (e.key === 's' || e.key === 'S') {
document.getElementById('btn-shot').click();
}
if (e.key === 'd' || e.key === 'D') {
document.getElementById('btn-defend').click();
}
if (e.key === 'l' || e.key === 'L') {
document.getElementById('btn-leave').click();
}
if (e.key === 'r' || e.key === 'R') {
document.getElementById('btn-reset').click();
}
if (e.key === 't' || e.key === 'T') {
document.getElementById('btn-toss').click();
}
// Camera shortcuts
if (e.key === '1') {
document.querySelector('[data-cam="broadcast"]')?.click();
}
if (e.key === '2') {
document.querySelector('[data-cam="bowler"]')?.click();
}
if (e.key === '3') {
document.querySelector('[data-cam="batsman"]')?.click();
}
if (e.key === '4') {
document.querySelector('[data-cam="behind"]')?.click();
}
if (e.key === '5') {
document.querySelector('[data-cam="field"]')?.click();
}
if (e.key === '6') {
document.querySelector('[data-cam="ball"]')?.click();
}
});
// -------- RESIZE --------
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// -------- INIT --------
setupTeams();
updateScoreboard();
showMessage('๐ Cricket Game', 'Press B to bowl, S to shot');
document.getElementById('match-type').textContent = 'T20';
// Start the game loop
requestAnimationFrame(gameLoop);
// Show power meter hint
document.getElementById('power-meter').classList.add('active');
console.log('๐ Cricket Game loaded!');
console.log('Controls: B=Bowl, S=Shot, D=Defend, L=Leave, R=Reset, T=Toss');
console.log('Camera: 1=Broadcast, 2=Bowler, 3=Batsman, 4=Behind, 5=Field, 6=Ball');
// -------- SIMPLE BALL TRAIL (visual) --------
// We'll use a simple line for the ball trail
const trailMat = new THREE.LineBasicMaterial({ color: 0xffdd44, transparent: true, opacity: 0.3 });
const trailGeo = new THREE.BufferGeometry();
const trailPositions = new Float32Array(30 * 3);
trailGeo.setAttribute('position', new THREE.BufferAttribute(trailPositions, 3));
const trailLine = new THREE.Line(trailGeo, trailMat);
scene.add(trailLine);
// Update trail in the loop
setInterval(() => {
if (ballInPlay && ball.userData.trail.length > 2) {
const positions = trailLine.geometry.attributes.position.array;
const trail = ball.userData.trail;
const count = Math.min(trail.length, 30);
for (let i = 0; i < count; i++) {
const idx = trail.length - count + i;
positions[i * 3] = trail[idx].x;
positions[i * 3 + 1] = trail[idx].y;
positions[i * 3 + 2] = trail[idx].z;
}
trailLine.geometry.attributes.position.needsUpdate = true;
trailLine.geometry.setDrawRange(0, count);
trailLine.visible = true;
} else {
trailLine.visible = false;
}
}, 50);
// Also add the ball glow effect
const glowMat = new THREE.MeshBasicMaterial({
color: 0xff6633,
transparent: true,
opacity: 0.15
});
const glow = new THREE.Mesh(new THREE.SphereGeometry(0.15, 12, 12), glowMat);
ball.add(glow);
console.log('โ
All systems ready!');
</script>
</body>
</html>Game Source: ๐ Realistic 3D Cricket Game
Creator: SuperWolf11
Libraries: three
Complexity: complex (1819 lines, 66.2 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: realistic-3d-cricket-game-superwolf11" to link back to the original. Then publish at arcadelab.ai/publish.