Spaces:
Sleeping
Sleeping
Polish RunRate Lab cricket bat and ball visuals
Browse files
app.py
CHANGED
|
@@ -738,9 +738,10 @@ GAME_HTML = r"""
|
|
| 738 |
position: absolute;
|
| 739 |
z-index: 6;
|
| 740 |
right: 16px;
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
|
|
|
| 744 |
overflow: auto;
|
| 745 |
border: 1px solid var(--line);
|
| 746 |
border-radius: 12px;
|
|
@@ -1080,6 +1081,7 @@ GAME_HTML = r"""
|
|
| 1080 |
.coach-panel {
|
| 1081 |
left: 14px;
|
| 1082 |
right: 14px;
|
|
|
|
| 1083 |
bottom: 14px;
|
| 1084 |
width: auto;
|
| 1085 |
max-height: 328px;
|
|
@@ -1327,6 +1329,7 @@ GAME_HTML = r"""
|
|
| 1327 |
log: [],
|
| 1328 |
particles: [],
|
| 1329 |
trail: [],
|
|
|
|
| 1330 |
resultFlash: 0,
|
| 1331 |
wicketShake: 0,
|
| 1332 |
processStreak: 0,
|
|
@@ -1508,6 +1511,7 @@ GAME_HTML = r"""
|
|
| 1508 |
state.deliveryLine = rand(-0.28, 0.28);
|
| 1509 |
state.ballProgress = 0;
|
| 1510 |
state.trail = [];
|
|
|
|
| 1511 |
state.message = `${pitch.Delivery}. Hold to charge. Release at impact.`;
|
| 1512 |
}
|
| 1513 |
|
|
@@ -1519,6 +1523,7 @@ GAME_HTML = r"""
|
|
| 1519 |
state.log = [];
|
| 1520 |
state.particles = [];
|
| 1521 |
state.trail = [];
|
|
|
|
| 1522 |
state.lastInsight = null;
|
| 1523 |
state.processSummary = null;
|
| 1524 |
state.processStreak = 0;
|
|
@@ -1621,7 +1626,9 @@ GAME_HTML = r"""
|
|
| 1621 |
state.message = "Review the analytics coach, then press Next Ball.";
|
| 1622 |
statusHint.textContent = "Review state. Press Next Ball or Enter when ready.";
|
| 1623 |
|
|
|
|
| 1624 |
if (!sampled.wicket && sampled.runs > 0) addShotTrail(sampled.runs);
|
|
|
|
| 1625 |
addImpactParticles(sampled.wicket ? fxColor("risk") : sampled.runs >= 4 ? fxColor("boundary") : fxColor("single"));
|
| 1626 |
if (sampled.runs >= 4) state.resultFlash = state.settings.reduceFlash ? 0.35 : 1;
|
| 1627 |
if (sampled.wicket) state.wicketShake = 1;
|
|
@@ -1649,6 +1656,7 @@ GAME_HTML = r"""
|
|
| 1649 |
state.ballProgress = 0;
|
| 1650 |
state.swingState = "ready";
|
| 1651 |
state.trail = [];
|
|
|
|
| 1652 |
nextBallBtn.classList.add("hidden");
|
| 1653 |
detailsBtn.classList.add("hidden");
|
| 1654 |
state.detailsOpen = false;
|
|
@@ -1972,6 +1980,7 @@ GAME_HTML = r"""
|
|
| 1972 |
|
| 1973 |
function drawBallPath() {
|
| 1974 |
if (!state.delivery) return;
|
|
|
|
| 1975 |
const start = pitchPoint(0.08, state.deliveryLine);
|
| 1976 |
const end = pitchPoint(0.88, state.deliveryLine * 0.25);
|
| 1977 |
ctx.save();
|
|
@@ -1990,23 +1999,59 @@ GAME_HTML = r"""
|
|
| 1990 |
ctx.restore();
|
| 1991 |
}
|
| 1992 |
|
| 1993 |
-
function
|
| 1994 |
-
if (state.mode !== "bowling" && state.mode !== "between") return;
|
| 1995 |
-
const p = ballPosition();
|
| 1996 |
-
if (state.mode === "between") return;
|
| 1997 |
ctx.save();
|
| 1998 |
-
ctx.
|
|
|
|
| 1999 |
ctx.beginPath();
|
| 2000 |
-
ctx.
|
| 2001 |
ctx.fill();
|
| 2002 |
-
|
| 2003 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2004 |
ctx.beginPath();
|
| 2005 |
-
ctx.arc(
|
| 2006 |
ctx.stroke();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2007 |
ctx.restore();
|
| 2008 |
}
|
| 2009 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2010 |
function drawShotTrail() {
|
| 2011 |
state.trail.forEach((trail) => {
|
| 2012 |
trail.life -= 0.018;
|
|
@@ -2024,21 +2069,65 @@ GAME_HTML = r"""
|
|
| 2024 |
state.trail = state.trail.filter((trail) => trail.life > 0);
|
| 2025 |
}
|
| 2026 |
|
| 2027 |
-
function
|
| 2028 |
const aim = rowBy(aims, "Aim", state.selectedAim);
|
| 2029 |
const angle = (Number(aim.Angle) - 90 + (state.selectedShot === "Cut / Pull" ? 14 : 0)) * Math.PI / 180;
|
| 2030 |
const start = pitchPoint(0.89, 0);
|
| 2031 |
const length = runs >= 6 ? height * 0.60 : runs === 4 ? height * 0.48 : height * 0.26;
|
| 2032 |
-
|
| 2033 |
x0: start.x,
|
| 2034 |
y0: start.y,
|
| 2035 |
cx: start.x + Math.cos(angle) * length * 0.38,
|
| 2036 |
cy: start.y + Math.sin(angle) * length * 0.36,
|
| 2037 |
x1: start.x + Math.cos(angle) * length,
|
| 2038 |
y1: start.y + Math.sin(angle) * length,
|
| 2039 |
-
runs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2040 |
life: 1
|
| 2041 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2042 |
}
|
| 2043 |
|
| 2044 |
function addImpactParticles(color) {
|
|
@@ -2105,43 +2194,41 @@ GAME_HTML = r"""
|
|
| 2105 |
const handleW = 7 * scale;
|
| 2106 |
const bladeTopW = 23 * scale;
|
| 2107 |
const bladeBotW = 42 * scale;
|
| 2108 |
-
const dx = Math.cos(angle);
|
| 2109 |
-
const dy = Math.sin(angle);
|
| 2110 |
-
const nx = -dy;
|
| 2111 |
-
const ny = dx;
|
| 2112 |
-
const hEnd = { x: anchor.x + dx * handleLen, y: anchor.y + dy * handleLen };
|
| 2113 |
-
const bEnd = { x: hEnd.x + dx * bladeLen, y: hEnd.y + dy * bladeLen };
|
| 2114 |
|
| 2115 |
ctx.save();
|
|
|
|
|
|
|
|
|
|
| 2116 |
ctx.strokeStyle = "#4a3417";
|
| 2117 |
ctx.lineWidth = handleW;
|
| 2118 |
ctx.lineCap = "round";
|
| 2119 |
ctx.beginPath();
|
| 2120 |
-
ctx.moveTo(
|
| 2121 |
-
ctx.lineTo(
|
| 2122 |
ctx.stroke();
|
| 2123 |
|
| 2124 |
ctx.strokeStyle = "#f8fafc";
|
| 2125 |
ctx.lineWidth = 2 * scale;
|
| 2126 |
for (let i = 0; i < 3; i += 1) {
|
| 2127 |
-
const gx =
|
| 2128 |
-
const gy = anchor.y + dy * (12 + i * 12) * scale;
|
| 2129 |
ctx.beginPath();
|
| 2130 |
-
ctx.moveTo(gx
|
| 2131 |
-
ctx.lineTo(gx
|
| 2132 |
ctx.stroke();
|
| 2133 |
}
|
| 2134 |
|
| 2135 |
-
const shoulder =
|
|
|
|
| 2136 |
ctx.fillStyle = "#d8ae55";
|
| 2137 |
ctx.strokeStyle = "#76551e";
|
| 2138 |
ctx.lineWidth = 3 * scale;
|
| 2139 |
ctx.beginPath();
|
| 2140 |
-
ctx.moveTo(
|
| 2141 |
-
ctx.
|
| 2142 |
-
ctx.lineTo(
|
| 2143 |
-
ctx.quadraticCurveTo(
|
| 2144 |
-
ctx.lineTo(shoulder
|
|
|
|
| 2145 |
ctx.closePath();
|
| 2146 |
ctx.fill();
|
| 2147 |
ctx.stroke();
|
|
@@ -2149,8 +2236,15 @@ GAME_HTML = r"""
|
|
| 2149 |
ctx.strokeStyle = "rgba(255,255,255,0.32)";
|
| 2150 |
ctx.lineWidth = 2 * scale;
|
| 2151 |
ctx.beginPath();
|
| 2152 |
-
ctx.moveTo(shoulder
|
| 2153 |
-
ctx.lineTo(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2154 |
ctx.stroke();
|
| 2155 |
ctx.restore();
|
| 2156 |
}
|
|
@@ -2162,16 +2256,17 @@ GAME_HTML = r"""
|
|
| 2162 |
const x = base.x + shake;
|
| 2163 |
const y = base.y + 18 * scale;
|
| 2164 |
const progress = clamp((now - state.swingStart) / 420, 0, 1);
|
|
|
|
| 2165 |
let batAngle = -1.25;
|
| 2166 |
let shoulderTilt = 0;
|
| 2167 |
if (state.mode === "bowling" && state.charging) {
|
| 2168 |
batAngle = -1.54 - clamp(state.power / 100, 0, 1) * 0.42;
|
| 2169 |
shoulderTilt = -0.10;
|
| 2170 |
} else if (state.swingState === "contact") {
|
| 2171 |
-
batAngle = -1.70 +
|
| 2172 |
-
shoulderTilt = 0.20 *
|
| 2173 |
} else if (state.swingState === "miss") {
|
| 2174 |
-
batAngle = -1.
|
| 2175 |
shoulderTilt = 0.10;
|
| 2176 |
} else {
|
| 2177 |
batAngle = -1.24;
|
|
@@ -2212,6 +2307,8 @@ GAME_HTML = r"""
|
|
| 2212 |
drawLimb(x + 48 * scale, shoulderY + 30 * scale, leftGlove.x, leftGlove.y, 15 * scale, "#f8fafc");
|
| 2213 |
drawLimb(leftGlove.x, leftGlove.y, rightGlove.x, rightGlove.y, 15 * scale, "#f8fafc");
|
| 2214 |
|
|
|
|
|
|
|
| 2215 |
ctx.fillStyle = "#facc15";
|
| 2216 |
ctx.beginPath();
|
| 2217 |
ctx.arc(leftGlove.x, leftGlove.y, 13 * scale, 0, Math.PI * 2);
|
|
@@ -2220,8 +2317,6 @@ GAME_HTML = r"""
|
|
| 2220 |
ctx.arc(rightGlove.x, rightGlove.y, 13 * scale, 0, Math.PI * 2);
|
| 2221 |
ctx.fill();
|
| 2222 |
|
| 2223 |
-
drawBat(leftGlove, rightGlove, batAngle, scale);
|
| 2224 |
-
|
| 2225 |
ctx.fillStyle = "#f8fafc";
|
| 2226 |
ctx.beginPath();
|
| 2227 |
ctx.arc(x, headY, 32 * scale, 0, Math.PI * 2);
|
|
@@ -2270,6 +2365,7 @@ GAME_HTML = r"""
|
|
| 2270 |
drawParticles();
|
| 2271 |
drawStumps(width / 2, pitchPoint(0.89, 0).y + 26, 1.15);
|
| 2272 |
drawBatter(now);
|
|
|
|
| 2273 |
drawScoreState();
|
| 2274 |
drawFlash();
|
| 2275 |
}
|
|
|
|
| 738 |
position: absolute;
|
| 739 |
z-index: 6;
|
| 740 |
right: 16px;
|
| 741 |
+
top: 128px;
|
| 742 |
+
bottom: auto;
|
| 743 |
+
width: min(360px, calc(100% - 32px));
|
| 744 |
+
max-height: min(430px, calc(100% - 154px));
|
| 745 |
overflow: auto;
|
| 746 |
border: 1px solid var(--line);
|
| 747 |
border-radius: 12px;
|
|
|
|
| 1081 |
.coach-panel {
|
| 1082 |
left: 14px;
|
| 1083 |
right: 14px;
|
| 1084 |
+
top: auto;
|
| 1085 |
bottom: 14px;
|
| 1086 |
width: auto;
|
| 1087 |
max-height: 328px;
|
|
|
|
| 1329 |
log: [],
|
| 1330 |
particles: [],
|
| 1331 |
trail: [],
|
| 1332 |
+
lastBallVisual: null,
|
| 1333 |
resultFlash: 0,
|
| 1334 |
wicketShake: 0,
|
| 1335 |
processStreak: 0,
|
|
|
|
| 1511 |
state.deliveryLine = rand(-0.28, 0.28);
|
| 1512 |
state.ballProgress = 0;
|
| 1513 |
state.trail = [];
|
| 1514 |
+
state.lastBallVisual = null;
|
| 1515 |
state.message = `${pitch.Delivery}. Hold to charge. Release at impact.`;
|
| 1516 |
}
|
| 1517 |
|
|
|
|
| 1523 |
state.log = [];
|
| 1524 |
state.particles = [];
|
| 1525 |
state.trail = [];
|
| 1526 |
+
state.lastBallVisual = null;
|
| 1527 |
state.lastInsight = null;
|
| 1528 |
state.processSummary = null;
|
| 1529 |
state.processStreak = 0;
|
|
|
|
| 1626 |
state.message = "Review the analytics coach, then press Next Ball.";
|
| 1627 |
statusHint.textContent = "Review state. Press Next Ball or Enter when ready.";
|
| 1628 |
|
| 1629 |
+
if (sampled.wicket) addWicketBallVisual();
|
| 1630 |
if (!sampled.wicket && sampled.runs > 0) addShotTrail(sampled.runs);
|
| 1631 |
+
if (!sampled.wicket && sampled.runs === 0) addDotBallVisual();
|
| 1632 |
addImpactParticles(sampled.wicket ? fxColor("risk") : sampled.runs >= 4 ? fxColor("boundary") : fxColor("single"));
|
| 1633 |
if (sampled.runs >= 4) state.resultFlash = state.settings.reduceFlash ? 0.35 : 1;
|
| 1634 |
if (sampled.wicket) state.wicketShake = 1;
|
|
|
|
| 1656 |
state.ballProgress = 0;
|
| 1657 |
state.swingState = "ready";
|
| 1658 |
state.trail = [];
|
| 1659 |
+
state.lastBallVisual = null;
|
| 1660 |
nextBallBtn.classList.add("hidden");
|
| 1661 |
detailsBtn.classList.add("hidden");
|
| 1662 |
state.detailsOpen = false;
|
|
|
|
| 1980 |
|
| 1981 |
function drawBallPath() {
|
| 1982 |
if (!state.delivery) return;
|
| 1983 |
+
if (state.mode === "between" || state.mode === "gameover") return;
|
| 1984 |
const start = pitchPoint(0.08, state.deliveryLine);
|
| 1985 |
const end = pitchPoint(0.88, state.deliveryLine * 0.25);
|
| 1986 |
ctx.save();
|
|
|
|
| 1999 |
ctx.restore();
|
| 2000 |
}
|
| 2001 |
|
| 2002 |
+
function drawCricketBall(x, y, r, rotation = 0, alpha = 1) {
|
|
|
|
|
|
|
|
|
|
| 2003 |
ctx.save();
|
| 2004 |
+
ctx.globalAlpha = alpha;
|
| 2005 |
+
ctx.fillStyle = "rgba(0,0,0,0.26)";
|
| 2006 |
ctx.beginPath();
|
| 2007 |
+
ctx.ellipse(x + r * 0.18, y + r * 0.78, r * 0.92, r * 0.30, 0, 0, Math.PI * 2);
|
| 2008 |
ctx.fill();
|
| 2009 |
+
|
| 2010 |
+
const gradient = ctx.createRadialGradient(x - r * 0.35, y - r * 0.35, r * 0.1, x, y, r);
|
| 2011 |
+
gradient.addColorStop(0, "#ff7a7a");
|
| 2012 |
+
gradient.addColorStop(0.58, "#ef4444");
|
| 2013 |
+
gradient.addColorStop(1, "#8f1f1f");
|
| 2014 |
+
ctx.fillStyle = gradient;
|
| 2015 |
+
ctx.beginPath();
|
| 2016 |
+
ctx.arc(x, y, r, 0, Math.PI * 2);
|
| 2017 |
+
ctx.fill();
|
| 2018 |
+
|
| 2019 |
+
ctx.translate(x, y);
|
| 2020 |
+
ctx.rotate(rotation);
|
| 2021 |
+
ctx.strokeStyle = "rgba(255,255,255,0.72)";
|
| 2022 |
+
ctx.lineWidth = Math.max(1.2, r * 0.15);
|
| 2023 |
+
ctx.lineCap = "round";
|
| 2024 |
ctx.beginPath();
|
| 2025 |
+
ctx.arc(-r * 0.18, -r * 0.02, r * 0.62, -0.95, 0.95);
|
| 2026 |
ctx.stroke();
|
| 2027 |
+
ctx.strokeStyle = "rgba(255,255,255,0.42)";
|
| 2028 |
+
ctx.lineWidth = Math.max(1, r * 0.08);
|
| 2029 |
+
for (let i = -2; i <= 2; i += 1) {
|
| 2030 |
+
ctx.beginPath();
|
| 2031 |
+
ctx.moveTo(-r * 0.24, i * r * 0.18);
|
| 2032 |
+
ctx.lineTo(-r * 0.08, i * r * 0.18 + r * 0.05);
|
| 2033 |
+
ctx.stroke();
|
| 2034 |
+
}
|
| 2035 |
ctx.restore();
|
| 2036 |
}
|
| 2037 |
|
| 2038 |
+
function drawBall() {
|
| 2039 |
+
if (state.mode !== "bowling") return;
|
| 2040 |
+
const p = ballPosition();
|
| 2041 |
+
drawCricketBall(p.x, p.y, p.r, p.t * Math.PI * 4, 1);
|
| 2042 |
+
}
|
| 2043 |
+
|
| 2044 |
+
function drawReviewBall(now) {
|
| 2045 |
+
if (state.mode !== "between" || !state.lastBallVisual) return;
|
| 2046 |
+
const visual = state.lastBallVisual;
|
| 2047 |
+
const elapsed = clamp((now - visual.createdAt) / 860, 0, 1);
|
| 2048 |
+
const ease = 1 - Math.pow(1 - elapsed, 2);
|
| 2049 |
+
const x = (1 - ease) * visual.x0 + ease * visual.x1;
|
| 2050 |
+
const curveY = (1 - ease) * (1 - ease) * visual.y0 + 2 * (1 - ease) * ease * visual.cy + ease * ease * visual.y1;
|
| 2051 |
+
const r = visual.wicket ? 9 : visual.runs >= 4 ? 8 : 7;
|
| 2052 |
+
drawCricketBall(x, curveY, r, now / 95, 0.94);
|
| 2053 |
+
}
|
| 2054 |
+
|
| 2055 |
function drawShotTrail() {
|
| 2056 |
state.trail.forEach((trail) => {
|
| 2057 |
trail.life -= 0.018;
|
|
|
|
| 2069 |
state.trail = state.trail.filter((trail) => trail.life > 0);
|
| 2070 |
}
|
| 2071 |
|
| 2072 |
+
function shotPathFor(runs) {
|
| 2073 |
const aim = rowBy(aims, "Aim", state.selectedAim);
|
| 2074 |
const angle = (Number(aim.Angle) - 90 + (state.selectedShot === "Cut / Pull" ? 14 : 0)) * Math.PI / 180;
|
| 2075 |
const start = pitchPoint(0.89, 0);
|
| 2076 |
const length = runs >= 6 ? height * 0.60 : runs === 4 ? height * 0.48 : height * 0.26;
|
| 2077 |
+
return {
|
| 2078 |
x0: start.x,
|
| 2079 |
y0: start.y,
|
| 2080 |
cx: start.x + Math.cos(angle) * length * 0.38,
|
| 2081 |
cy: start.y + Math.sin(angle) * length * 0.36,
|
| 2082 |
x1: start.x + Math.cos(angle) * length,
|
| 2083 |
y1: start.y + Math.sin(angle) * length,
|
| 2084 |
+
runs
|
| 2085 |
+
};
|
| 2086 |
+
}
|
| 2087 |
+
|
| 2088 |
+
function addShotTrail(runs) {
|
| 2089 |
+
const path = shotPathFor(runs);
|
| 2090 |
+
state.trail.push({
|
| 2091 |
+
...path,
|
| 2092 |
life: 1
|
| 2093 |
});
|
| 2094 |
+
state.lastBallVisual = {
|
| 2095 |
+
...path,
|
| 2096 |
+
wicket: false,
|
| 2097 |
+
createdAt: performance.now()
|
| 2098 |
+
};
|
| 2099 |
+
}
|
| 2100 |
+
|
| 2101 |
+
function addWicketBallVisual() {
|
| 2102 |
+
const start = pitchPoint(0.89, 0);
|
| 2103 |
+
const side = state.deliveryLine < 0 ? -1 : 1;
|
| 2104 |
+
state.lastBallVisual = {
|
| 2105 |
+
x0: start.x + side * 10,
|
| 2106 |
+
y0: start.y - 16,
|
| 2107 |
+
cx: start.x + side * rand(26, 40),
|
| 2108 |
+
cy: start.y - rand(24, 42),
|
| 2109 |
+
x1: start.x + side * rand(48, 72),
|
| 2110 |
+
y1: start.y + rand(0, 12),
|
| 2111 |
+
runs: 0,
|
| 2112 |
+
wicket: true,
|
| 2113 |
+
createdAt: performance.now()
|
| 2114 |
+
};
|
| 2115 |
+
}
|
| 2116 |
+
|
| 2117 |
+
function addDotBallVisual() {
|
| 2118 |
+
const start = pitchPoint(0.89, state.deliveryLine * 0.15);
|
| 2119 |
+
const side = state.selectedAim === "Leg Side" ? -1 : 1;
|
| 2120 |
+
state.lastBallVisual = {
|
| 2121 |
+
x0: start.x + side * 12,
|
| 2122 |
+
y0: start.y - 14,
|
| 2123 |
+
cx: start.x + side * rand(26, 42),
|
| 2124 |
+
cy: start.y - rand(18, 34),
|
| 2125 |
+
x1: start.x + side * rand(52, 82),
|
| 2126 |
+
y1: start.y + rand(2, 16),
|
| 2127 |
+
runs: 0,
|
| 2128 |
+
wicket: false,
|
| 2129 |
+
createdAt: performance.now()
|
| 2130 |
+
};
|
| 2131 |
}
|
| 2132 |
|
| 2133 |
function addImpactParticles(color) {
|
|
|
|
| 2194 |
const handleW = 7 * scale;
|
| 2195 |
const bladeTopW = 23 * scale;
|
| 2196 |
const bladeBotW = 42 * scale;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2197 |
|
| 2198 |
ctx.save();
|
| 2199 |
+
ctx.translate(anchor.x, anchor.y);
|
| 2200 |
+
ctx.rotate(angle);
|
| 2201 |
+
|
| 2202 |
ctx.strokeStyle = "#4a3417";
|
| 2203 |
ctx.lineWidth = handleW;
|
| 2204 |
ctx.lineCap = "round";
|
| 2205 |
ctx.beginPath();
|
| 2206 |
+
ctx.moveTo(0, 0);
|
| 2207 |
+
ctx.lineTo(handleLen, 0);
|
| 2208 |
ctx.stroke();
|
| 2209 |
|
| 2210 |
ctx.strokeStyle = "#f8fafc";
|
| 2211 |
ctx.lineWidth = 2 * scale;
|
| 2212 |
for (let i = 0; i < 3; i += 1) {
|
| 2213 |
+
const gx = (13 + i * 12) * scale;
|
|
|
|
| 2214 |
ctx.beginPath();
|
| 2215 |
+
ctx.moveTo(gx, -6 * scale);
|
| 2216 |
+
ctx.lineTo(gx, 6 * scale);
|
| 2217 |
ctx.stroke();
|
| 2218 |
}
|
| 2219 |
|
| 2220 |
+
const shoulder = handleLen + 10 * scale;
|
| 2221 |
+
const toe = handleLen + bladeLen;
|
| 2222 |
ctx.fillStyle = "#d8ae55";
|
| 2223 |
ctx.strokeStyle = "#76551e";
|
| 2224 |
ctx.lineWidth = 3 * scale;
|
| 2225 |
ctx.beginPath();
|
| 2226 |
+
ctx.moveTo(handleLen, -bladeTopW * 0.44);
|
| 2227 |
+
ctx.quadraticCurveTo(shoulder - 4 * scale, -bladeTopW * 0.56, shoulder, -bladeTopW * 0.5);
|
| 2228 |
+
ctx.lineTo(toe - 16 * scale, -bladeBotW * 0.5);
|
| 2229 |
+
ctx.quadraticCurveTo(toe + 9 * scale, 0, toe - 16 * scale, bladeBotW * 0.5);
|
| 2230 |
+
ctx.lineTo(shoulder, bladeTopW * 0.5);
|
| 2231 |
+
ctx.quadraticCurveTo(shoulder - 5 * scale, bladeTopW * 0.58, handleLen, bladeTopW * 0.44);
|
| 2232 |
ctx.closePath();
|
| 2233 |
ctx.fill();
|
| 2234 |
ctx.stroke();
|
|
|
|
| 2236 |
ctx.strokeStyle = "rgba(255,255,255,0.32)";
|
| 2237 |
ctx.lineWidth = 2 * scale;
|
| 2238 |
ctx.beginPath();
|
| 2239 |
+
ctx.moveTo(shoulder + 8 * scale, -4 * scale);
|
| 2240 |
+
ctx.lineTo(toe - 20 * scale, -11 * scale);
|
| 2241 |
+
ctx.stroke();
|
| 2242 |
+
|
| 2243 |
+
ctx.strokeStyle = "rgba(118,85,30,0.35)";
|
| 2244 |
+
ctx.lineWidth = 1.5 * scale;
|
| 2245 |
+
ctx.beginPath();
|
| 2246 |
+
ctx.moveTo(shoulder + 5 * scale, 0);
|
| 2247 |
+
ctx.lineTo(toe - 22 * scale, 0);
|
| 2248 |
ctx.stroke();
|
| 2249 |
ctx.restore();
|
| 2250 |
}
|
|
|
|
| 2256 |
const x = base.x + shake;
|
| 2257 |
const y = base.y + 18 * scale;
|
| 2258 |
const progress = clamp((now - state.swingStart) / 420, 0, 1);
|
| 2259 |
+
const finishProgress = state.mode === "between" ? 0.76 : progress;
|
| 2260 |
let batAngle = -1.25;
|
| 2261 |
let shoulderTilt = 0;
|
| 2262 |
if (state.mode === "bowling" && state.charging) {
|
| 2263 |
batAngle = -1.54 - clamp(state.power / 100, 0, 1) * 0.42;
|
| 2264 |
shoulderTilt = -0.10;
|
| 2265 |
} else if (state.swingState === "contact") {
|
| 2266 |
+
batAngle = -1.70 + finishProgress * 1.28;
|
| 2267 |
+
shoulderTilt = 0.20 * finishProgress;
|
| 2268 |
} else if (state.swingState === "miss") {
|
| 2269 |
+
batAngle = -1.58 + finishProgress * 0.88;
|
| 2270 |
shoulderTilt = 0.10;
|
| 2271 |
} else {
|
| 2272 |
batAngle = -1.24;
|
|
|
|
| 2307 |
drawLimb(x + 48 * scale, shoulderY + 30 * scale, leftGlove.x, leftGlove.y, 15 * scale, "#f8fafc");
|
| 2308 |
drawLimb(leftGlove.x, leftGlove.y, rightGlove.x, rightGlove.y, 15 * scale, "#f8fafc");
|
| 2309 |
|
| 2310 |
+
drawBat(leftGlove, rightGlove, batAngle, scale);
|
| 2311 |
+
|
| 2312 |
ctx.fillStyle = "#facc15";
|
| 2313 |
ctx.beginPath();
|
| 2314 |
ctx.arc(leftGlove.x, leftGlove.y, 13 * scale, 0, Math.PI * 2);
|
|
|
|
| 2317 |
ctx.arc(rightGlove.x, rightGlove.y, 13 * scale, 0, Math.PI * 2);
|
| 2318 |
ctx.fill();
|
| 2319 |
|
|
|
|
|
|
|
| 2320 |
ctx.fillStyle = "#f8fafc";
|
| 2321 |
ctx.beginPath();
|
| 2322 |
ctx.arc(x, headY, 32 * scale, 0, Math.PI * 2);
|
|
|
|
| 2365 |
drawParticles();
|
| 2366 |
drawStumps(width / 2, pitchPoint(0.89, 0).y + 26, 1.15);
|
| 2367 |
drawBatter(now);
|
| 2368 |
+
drawReviewBall(now);
|
| 2369 |
drawScoreState();
|
| 2370 |
drawFlash();
|
| 2371 |
}
|