@guinetik/gcanvas 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CNAME +1 -0
- package/dist/animations.html +31 -0
- package/dist/basic.html +38 -0
- package/dist/baskara.html +31 -0
- package/dist/bezier.html +35 -0
- package/dist/beziersignature.html +29 -0
- package/dist/blackhole.html +28 -0
- package/dist/blob.html +35 -0
- package/dist/coordinates.html +698 -0
- package/dist/cube3d.html +23 -0
- package/dist/demos.css +303 -0
- package/dist/dino.html +42 -0
- package/dist/easing.html +28 -0
- package/dist/events.html +195 -0
- package/dist/fluent.html +647 -0
- package/dist/fluid-simple.html +22 -0
- package/dist/fluid.html +37 -0
- package/dist/fractals.html +36 -0
- package/dist/gameobjects.html +626 -0
- package/dist/gcanvas.es.js +517 -0
- package/dist/gcanvas.es.min.js +1 -1
- package/dist/gcanvas.umd.js +1 -1
- package/dist/gcanvas.umd.min.js +1 -1
- package/dist/genart.html +26 -0
- package/dist/gendream.html +26 -0
- package/dist/group.html +36 -0
- package/dist/home.html +587 -0
- package/dist/hyperbolic001.html +23 -0
- package/dist/hyperbolic002.html +23 -0
- package/dist/hyperbolic003.html +23 -0
- package/dist/hyperbolic004.html +23 -0
- package/dist/hyperbolic005.html +22 -0
- package/dist/index.html +398 -0
- package/dist/isometric.html +34 -0
- package/dist/js/animations.js +452 -0
- package/dist/js/basic.js +204 -0
- package/dist/js/baskara.js +751 -0
- package/dist/js/bezier.js +692 -0
- package/dist/js/beziersignature.js +241 -0
- package/dist/js/blackhole/accretiondisk.obj.js +379 -0
- package/dist/js/blackhole/blackhole.obj.js +318 -0
- package/dist/js/blackhole/index.js +409 -0
- package/dist/js/blackhole/particle.js +56 -0
- package/dist/js/blackhole/starfield.obj.js +218 -0
- package/dist/js/blob.js +2276 -0
- package/dist/js/coordinates.js +840 -0
- package/dist/js/cube3d.js +789 -0
- package/dist/js/dino.js +1420 -0
- package/dist/js/easing.js +477 -0
- package/dist/js/fluent.js +183 -0
- package/dist/js/fluid-simple.js +253 -0
- package/dist/js/fluid.js +527 -0
- package/dist/js/fractals.js +932 -0
- package/dist/js/fractalworker.js +93 -0
- package/dist/js/gameobjects.js +176 -0
- package/dist/js/genart.js +268 -0
- package/dist/js/gendream.js +209 -0
- package/dist/js/group.js +140 -0
- package/dist/js/hyperbolic001.js +310 -0
- package/dist/js/hyperbolic002.js +388 -0
- package/dist/js/hyperbolic003.js +319 -0
- package/dist/js/hyperbolic004.js +345 -0
- package/dist/js/hyperbolic005.js +340 -0
- package/dist/js/info-toggle.js +25 -0
- package/dist/js/isometric.js +863 -0
- package/dist/js/kerr.js +1547 -0
- package/dist/js/lavalamp.js +590 -0
- package/dist/js/layout.js +354 -0
- package/dist/js/mondrian.js +285 -0
- package/dist/js/opacity.js +275 -0
- package/dist/js/painter.js +484 -0
- package/dist/js/particles-showcase.js +514 -0
- package/dist/js/particles.js +299 -0
- package/dist/js/patterns.js +397 -0
- package/dist/js/penrose/artifact.js +69 -0
- package/dist/js/penrose/blackhole.js +121 -0
- package/dist/js/penrose/constants.js +73 -0
- package/dist/js/penrose/game.js +943 -0
- package/dist/js/penrose/lore.js +278 -0
- package/dist/js/penrose/penrosescene.js +892 -0
- package/dist/js/penrose/ship.js +216 -0
- package/dist/js/penrose/sounds.js +211 -0
- package/dist/js/penrose/voidparticle.js +55 -0
- package/dist/js/penrose/voidscene.js +258 -0
- package/dist/js/penrose/voidship.js +144 -0
- package/dist/js/penrose/wormhole.js +46 -0
- package/dist/js/pipeline.js +555 -0
- package/dist/js/plane3d.js +256 -0
- package/dist/js/platformer.js +1579 -0
- package/dist/js/scene.js +304 -0
- package/dist/js/scenes.js +320 -0
- package/dist/js/schrodinger.js +410 -0
- package/dist/js/schwarzschild.js +1015 -0
- package/dist/js/shapes.js +628 -0
- package/dist/js/space/alien.js +171 -0
- package/dist/js/space/boom.js +98 -0
- package/dist/js/space/boss.js +353 -0
- package/dist/js/space/buff.js +73 -0
- package/dist/js/space/bullet.js +102 -0
- package/dist/js/space/constants.js +85 -0
- package/dist/js/space/game.js +1884 -0
- package/dist/js/space/hud.js +112 -0
- package/dist/js/space/laserbeam.js +179 -0
- package/dist/js/space/lightning.js +277 -0
- package/dist/js/space/minion.js +192 -0
- package/dist/js/space/missile.js +212 -0
- package/dist/js/space/player.js +430 -0
- package/dist/js/space/powerup.js +90 -0
- package/dist/js/space/starfield.js +58 -0
- package/dist/js/space/starpower.js +90 -0
- package/dist/js/spacetime.js +559 -0
- package/dist/js/sphere3d.js +229 -0
- package/dist/js/sprite.js +473 -0
- package/dist/js/starfaux/config.js +118 -0
- package/dist/js/starfaux/enemy.js +353 -0
- package/dist/js/starfaux/hud.js +78 -0
- package/dist/js/starfaux/index.js +482 -0
- package/dist/js/starfaux/laser.js +182 -0
- package/dist/js/starfaux/player.js +468 -0
- package/dist/js/starfaux/terrain.js +560 -0
- package/dist/js/study001.js +275 -0
- package/dist/js/study002.js +366 -0
- package/dist/js/study003.js +331 -0
- package/dist/js/study004.js +389 -0
- package/dist/js/study005.js +209 -0
- package/dist/js/study006.js +194 -0
- package/dist/js/study007.js +192 -0
- package/dist/js/study008.js +413 -0
- package/dist/js/svgtween.js +204 -0
- package/dist/js/tde/accretiondisk.js +471 -0
- package/dist/js/tde/blackhole.js +219 -0
- package/dist/js/tde/blackholescene.js +209 -0
- package/dist/js/tde/config.js +59 -0
- package/dist/js/tde/index.js +820 -0
- package/dist/js/tde/jets.js +290 -0
- package/dist/js/tde/lensedstarfield.js +154 -0
- package/dist/js/tde/tdestar.js +297 -0
- package/dist/js/tde/tidalstream.js +372 -0
- package/dist/js/tde_old/blackhole.obj.js +354 -0
- package/dist/js/tde_old/debris.obj.js +791 -0
- package/dist/js/tde_old/flare.obj.js +239 -0
- package/dist/js/tde_old/index.js +448 -0
- package/dist/js/tde_old/star.obj.js +812 -0
- package/dist/js/tetris/config.js +157 -0
- package/dist/js/tetris/grid.js +286 -0
- package/dist/js/tetris/index.js +1195 -0
- package/dist/js/tetris/renderer.js +634 -0
- package/dist/js/tetris/tetrominos.js +280 -0
- package/dist/js/tiles.js +312 -0
- package/dist/js/tweendemo.js +79 -0
- package/dist/js/visibility.js +102 -0
- package/dist/kerr.html +28 -0
- package/dist/lavalamp.html +27 -0
- package/dist/layouts.html +37 -0
- package/dist/logo.svg +4 -0
- package/dist/loop.html +84 -0
- package/dist/mondrian.html +32 -0
- package/dist/og_image.png +0 -0
- package/dist/opacity.html +36 -0
- package/dist/painter.html +39 -0
- package/dist/particles-showcase.html +28 -0
- package/dist/particles.html +24 -0
- package/dist/patterns.html +33 -0
- package/dist/penrose-game.html +31 -0
- package/dist/pipeline.html +737 -0
- package/dist/plane3d.html +24 -0
- package/dist/platformer.html +43 -0
- package/dist/scene.html +33 -0
- package/dist/scenes.html +96 -0
- package/dist/schrodinger.html +27 -0
- package/dist/schwarzschild.html +27 -0
- package/dist/shapes.html +16 -0
- package/dist/space.html +85 -0
- package/dist/spacetime.html +27 -0
- package/dist/sphere3d.html +24 -0
- package/dist/sprite.html +18 -0
- package/dist/starfaux.html +22 -0
- package/dist/study001.html +23 -0
- package/dist/study002.html +23 -0
- package/dist/study003.html +23 -0
- package/dist/study004.html +23 -0
- package/dist/study005.html +22 -0
- package/dist/study006.html +24 -0
- package/dist/study007.html +24 -0
- package/dist/study008.html +22 -0
- package/dist/svgtween.html +29 -0
- package/dist/tde.html +28 -0
- package/dist/tetris3d.html +25 -0
- package/dist/tiles.html +28 -0
- package/dist/transforms.html +400 -0
- package/dist/tween.html +45 -0
- package/dist/visibility.html +33 -0
- package/package.json +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 3D Tetris Configuration
|
|
3
|
+
* All game constants and settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const CONFIG = {
|
|
7
|
+
// Grid dimensions
|
|
8
|
+
grid: {
|
|
9
|
+
width: 6, // X axis
|
|
10
|
+
depth: 6, // Z axis
|
|
11
|
+
height: 16, // Y axis (pieces fall down Y)
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
// Timing (in seconds)
|
|
15
|
+
timing: {
|
|
16
|
+
fallSpeed: 1.0, // Seconds per row at level 1
|
|
17
|
+
lockDelay: 0.5, // Seconds before piece locks after landing
|
|
18
|
+
softDropMultiplier: 15, // Speed multiplier when holding down
|
|
19
|
+
moveRepeatDelay: 0.15, // Delay before key repeat starts
|
|
20
|
+
moveRepeatRate: 0.05, // Rate of key repeat
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// Camera settings
|
|
24
|
+
camera: {
|
|
25
|
+
perspective: 800,
|
|
26
|
+
rotationX: 0.4,
|
|
27
|
+
rotationY: -0.5,
|
|
28
|
+
inertia: true,
|
|
29
|
+
friction: 0.95,
|
|
30
|
+
// Preset camera views for cycling
|
|
31
|
+
presets: [
|
|
32
|
+
{ name: "Default", rotationX: 0.4, rotationY: -0.5 },
|
|
33
|
+
{ name: "Front", rotationX: 0.3, rotationY: 0 },
|
|
34
|
+
{ name: "Side", rotationX: 0.3, rotationY: -Math.PI / 2 },
|
|
35
|
+
{ name: "Top", rotationX: 1.2, rotationY: 0 },
|
|
36
|
+
{ name: "Isometric", rotationX: 0.6, rotationY: -0.78 },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// Visual settings (cubeSize is calculated dynamically based on screen)
|
|
41
|
+
visual: {
|
|
42
|
+
// These are fractions of min(width, height) for responsive scaling
|
|
43
|
+
cubeSizeFraction: 0.055, // Cube size as fraction of screen (bigger = fills more screen)
|
|
44
|
+
cubeGapFraction: 0.003, // Gap between cubes
|
|
45
|
+
wellColor: "#00FF41", // Terminal green
|
|
46
|
+
wellLineWidth: 2,
|
|
47
|
+
ghostAlpha: 0.25,
|
|
48
|
+
backgroundColor: "#000000",
|
|
49
|
+
|
|
50
|
+
// Sticker mode for cubes
|
|
51
|
+
stickerMode: true,
|
|
52
|
+
stickerMargin: 0.1,
|
|
53
|
+
stickerBackgroundColor: "#0A0A0A",
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// Piece colors (classic Tetris colors)
|
|
57
|
+
pieceColors: {
|
|
58
|
+
I: "#00FFFF", // Cyan
|
|
59
|
+
O: "#FFFF00", // Yellow
|
|
60
|
+
T: "#AA00FF", // Purple
|
|
61
|
+
S: "#00FF00", // Green
|
|
62
|
+
Z: "#FF0000", // Red
|
|
63
|
+
L: "#FFA500", // Orange
|
|
64
|
+
J: "#0066FF", // Blue
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// Scoring
|
|
68
|
+
scoring: {
|
|
69
|
+
single: 100,
|
|
70
|
+
double: 300,
|
|
71
|
+
triple: 500,
|
|
72
|
+
tetris: 800, // 4 lines at once
|
|
73
|
+
softDrop: 1, // Per cell
|
|
74
|
+
hardDrop: 2, // Per cell
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// Leveling
|
|
78
|
+
leveling: {
|
|
79
|
+
linesPerLevel: 10,
|
|
80
|
+
speedMultiplier: 0.85, // Multiply fall time by this each level
|
|
81
|
+
maxLevel: 15,
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// Sound (placeholder for future)
|
|
85
|
+
sound: {
|
|
86
|
+
enabled: false,
|
|
87
|
+
masterVolume: 0.5,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Tetromino shape definitions
|
|
93
|
+
* Each shape is a 2D array representing the XZ plane
|
|
94
|
+
* Pieces fall along the Y axis
|
|
95
|
+
*/
|
|
96
|
+
export const SHAPES = {
|
|
97
|
+
I: {
|
|
98
|
+
// Long bar - 4x1
|
|
99
|
+
matrix: [
|
|
100
|
+
[1, 1, 1, 1],
|
|
101
|
+
],
|
|
102
|
+
color: CONFIG.pieceColors.I,
|
|
103
|
+
},
|
|
104
|
+
O: {
|
|
105
|
+
// Square - 2x2
|
|
106
|
+
matrix: [
|
|
107
|
+
[1, 1],
|
|
108
|
+
[1, 1],
|
|
109
|
+
],
|
|
110
|
+
color: CONFIG.pieceColors.O,
|
|
111
|
+
},
|
|
112
|
+
T: {
|
|
113
|
+
// T-shape - 3x2
|
|
114
|
+
matrix: [
|
|
115
|
+
[1, 1, 1],
|
|
116
|
+
[0, 1, 0],
|
|
117
|
+
],
|
|
118
|
+
color: CONFIG.pieceColors.T,
|
|
119
|
+
},
|
|
120
|
+
S: {
|
|
121
|
+
// S-shape - 3x2
|
|
122
|
+
matrix: [
|
|
123
|
+
[0, 1, 1],
|
|
124
|
+
[1, 1, 0],
|
|
125
|
+
],
|
|
126
|
+
color: CONFIG.pieceColors.S,
|
|
127
|
+
},
|
|
128
|
+
Z: {
|
|
129
|
+
// Z-shape - 3x2
|
|
130
|
+
matrix: [
|
|
131
|
+
[1, 1, 0],
|
|
132
|
+
[0, 1, 1],
|
|
133
|
+
],
|
|
134
|
+
color: CONFIG.pieceColors.Z,
|
|
135
|
+
},
|
|
136
|
+
L: {
|
|
137
|
+
// L-shape - 2x3
|
|
138
|
+
matrix: [
|
|
139
|
+
[1, 0],
|
|
140
|
+
[1, 0],
|
|
141
|
+
[1, 1],
|
|
142
|
+
],
|
|
143
|
+
color: CONFIG.pieceColors.L,
|
|
144
|
+
},
|
|
145
|
+
J: {
|
|
146
|
+
// J-shape - 2x3
|
|
147
|
+
matrix: [
|
|
148
|
+
[0, 1],
|
|
149
|
+
[0, 1],
|
|
150
|
+
[1, 1],
|
|
151
|
+
],
|
|
152
|
+
color: CONFIG.pieceColors.J,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// List of all piece types for random selection
|
|
157
|
+
export const PIECE_TYPES = Object.keys(SHAPES);
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid - 3D grid for tracking placed blocks
|
|
3
|
+
*
|
|
4
|
+
* Coordinate system:
|
|
5
|
+
* - X: left to right (0 to width-1)
|
|
6
|
+
* - Y: top to bottom (0 to height-1, 0 is top)
|
|
7
|
+
* - Z: front to back (0 to depth-1)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { CONFIG } from "./config.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Cell data structure
|
|
14
|
+
* @typedef {Object} Cell
|
|
15
|
+
* @property {boolean} filled - Whether the cell is occupied
|
|
16
|
+
* @property {string|null} color - Color of the block, or null if empty
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export class Grid {
|
|
20
|
+
constructor() {
|
|
21
|
+
const { width, depth, height } = CONFIG.grid;
|
|
22
|
+
this.width = width;
|
|
23
|
+
this.depth = depth;
|
|
24
|
+
this.height = height;
|
|
25
|
+
|
|
26
|
+
// 3D array: cells[x][y][z]
|
|
27
|
+
this.cells = [];
|
|
28
|
+
|
|
29
|
+
this._init();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initialize empty grid
|
|
34
|
+
* @private
|
|
35
|
+
*/
|
|
36
|
+
_init() {
|
|
37
|
+
this.cells = [];
|
|
38
|
+
for (let x = 0; x < this.width; x++) {
|
|
39
|
+
this.cells[x] = [];
|
|
40
|
+
for (let y = 0; y < this.height; y++) {
|
|
41
|
+
this.cells[x][y] = [];
|
|
42
|
+
for (let z = 0; z < this.depth; z++) {
|
|
43
|
+
this.cells[x][y][z] = { filled: false, color: null };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Clear the grid (for game restart)
|
|
51
|
+
*/
|
|
52
|
+
clear() {
|
|
53
|
+
this._init();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if coordinates are within grid bounds
|
|
58
|
+
* @param {number} x
|
|
59
|
+
* @param {number} y
|
|
60
|
+
* @param {number} z
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
isInBounds(x, y, z) {
|
|
64
|
+
return (
|
|
65
|
+
x >= 0 &&
|
|
66
|
+
x < this.width &&
|
|
67
|
+
y >= 0 &&
|
|
68
|
+
y < this.height &&
|
|
69
|
+
z >= 0 &&
|
|
70
|
+
z < this.depth
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if a cell is occupied (or out of bounds)
|
|
76
|
+
* @param {number} x
|
|
77
|
+
* @param {number} y
|
|
78
|
+
* @param {number} z
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
isOccupied(x, y, z) {
|
|
82
|
+
// Out of bounds on sides/bottom counts as occupied
|
|
83
|
+
if (x < 0 || x >= this.width || z < 0 || z >= this.depth) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
// Above grid is not occupied (pieces spawn there)
|
|
87
|
+
if (y < 0) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
// Below grid counts as occupied (floor)
|
|
91
|
+
if (y >= this.height) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
return this.cells[x][y][z].filled;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if a piece can be placed at given positions
|
|
99
|
+
* @param {Array<{x: number, y: number, z: number}>} positions
|
|
100
|
+
* @returns {boolean}
|
|
101
|
+
*/
|
|
102
|
+
canPlace(positions) {
|
|
103
|
+
for (const pos of positions) {
|
|
104
|
+
if (this.isOccupied(pos.x, pos.y, pos.z)) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Lock a piece into the grid
|
|
113
|
+
* @param {Array<{x: number, y: number, z: number}>} positions
|
|
114
|
+
* @param {string} color
|
|
115
|
+
*/
|
|
116
|
+
placePiece(positions, color) {
|
|
117
|
+
for (const pos of positions) {
|
|
118
|
+
if (this.isInBounds(pos.x, pos.y, pos.z)) {
|
|
119
|
+
this.cells[pos.x][pos.y][pos.z] = {
|
|
120
|
+
filled: true,
|
|
121
|
+
color: color,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check for complete layers and clear them
|
|
129
|
+
* @returns {{clearedCount: number, clearedLayers: number[]}}
|
|
130
|
+
*/
|
|
131
|
+
checkAndClearLayers() {
|
|
132
|
+
const clearedLayers = [];
|
|
133
|
+
|
|
134
|
+
// Check each layer from bottom to top
|
|
135
|
+
for (let y = this.height - 1; y >= 0; y--) {
|
|
136
|
+
if (this._isLayerComplete(y)) {
|
|
137
|
+
clearedLayers.push(y);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Clear the layers (from bottom to top to maintain indices)
|
|
142
|
+
clearedLayers.sort((a, b) => b - a);
|
|
143
|
+
for (const y of clearedLayers) {
|
|
144
|
+
this._clearLayer(y);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
clearedCount: clearedLayers.length,
|
|
149
|
+
clearedLayers: clearedLayers,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if a horizontal layer is completely filled
|
|
155
|
+
* @param {number} y - Layer Y coordinate
|
|
156
|
+
* @returns {boolean}
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
159
|
+
_isLayerComplete(y) {
|
|
160
|
+
for (let x = 0; x < this.width; x++) {
|
|
161
|
+
for (let z = 0; z < this.depth; z++) {
|
|
162
|
+
if (!this.cells[x][y][z].filled) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Clear a layer and drop everything above
|
|
172
|
+
* @param {number} clearedY - Layer to clear
|
|
173
|
+
* @private
|
|
174
|
+
*/
|
|
175
|
+
_clearLayer(clearedY) {
|
|
176
|
+
// Move all layers above down by one
|
|
177
|
+
for (let y = clearedY; y > 0; y--) {
|
|
178
|
+
for (let x = 0; x < this.width; x++) {
|
|
179
|
+
for (let z = 0; z < this.depth; z++) {
|
|
180
|
+
this.cells[x][y][z] = { ...this.cells[x][y - 1][z] };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Clear the top layer
|
|
186
|
+
for (let x = 0; x < this.width; x++) {
|
|
187
|
+
for (let z = 0; z < this.depth; z++) {
|
|
188
|
+
this.cells[x][0][z] = { filled: false, color: null };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get all filled cells for rendering
|
|
195
|
+
* @returns {Array<{x: number, y: number, z: number, color: string}>}
|
|
196
|
+
*/
|
|
197
|
+
getFilledCells() {
|
|
198
|
+
const filled = [];
|
|
199
|
+
|
|
200
|
+
for (let x = 0; x < this.width; x++) {
|
|
201
|
+
for (let y = 0; y < this.height; y++) {
|
|
202
|
+
for (let z = 0; z < this.depth; z++) {
|
|
203
|
+
const cell = this.cells[x][y][z];
|
|
204
|
+
if (cell.filled) {
|
|
205
|
+
filled.push({
|
|
206
|
+
x: x,
|
|
207
|
+
y: y,
|
|
208
|
+
z: z,
|
|
209
|
+
color: cell.color,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return filled;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get the highest filled Y coordinate in each column
|
|
221
|
+
* Used for efficient ghost piece calculation
|
|
222
|
+
* @returns {number[][]} 2D array [x][z] of highest filled Y (or height if empty)
|
|
223
|
+
*/
|
|
224
|
+
getColumnHeights() {
|
|
225
|
+
const heights = [];
|
|
226
|
+
|
|
227
|
+
for (let x = 0; x < this.width; x++) {
|
|
228
|
+
heights[x] = [];
|
|
229
|
+
for (let z = 0; z < this.depth; z++) {
|
|
230
|
+
heights[x][z] = this.height; // Default to bottom
|
|
231
|
+
for (let y = 0; y < this.height; y++) {
|
|
232
|
+
if (this.cells[x][y][z].filled) {
|
|
233
|
+
heights[x][z] = y;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return heights;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Check if the game is over (blocks above playfield)
|
|
245
|
+
* @returns {boolean}
|
|
246
|
+
*/
|
|
247
|
+
isGameOver() {
|
|
248
|
+
// Check if any blocks are in the top 2 rows
|
|
249
|
+
for (let x = 0; x < this.width; x++) {
|
|
250
|
+
for (let z = 0; z < this.depth; z++) {
|
|
251
|
+
if (this.cells[x][0][z].filled || this.cells[x][1][z].filled) {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Calculate where a piece would land (for ghost piece)
|
|
261
|
+
* @param {TetrisPiece} piece
|
|
262
|
+
* @returns {number} The Y coordinate where piece would land
|
|
263
|
+
*/
|
|
264
|
+
calculateLandingY(piece) {
|
|
265
|
+
let testY = piece.y;
|
|
266
|
+
|
|
267
|
+
while (true) {
|
|
268
|
+
testY++;
|
|
269
|
+
// Use voxels to calculate positions at testY
|
|
270
|
+
const positions = piece.voxels.map((v) => ({
|
|
271
|
+
x: piece.x + v.x,
|
|
272
|
+
y: testY + v.y,
|
|
273
|
+
z: piece.z + v.z,
|
|
274
|
+
}));
|
|
275
|
+
|
|
276
|
+
if (!this.canPlace(positions)) {
|
|
277
|
+
return testY - 1;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Safety check to prevent infinite loop
|
|
281
|
+
if (testY > this.height + 5) {
|
|
282
|
+
return piece.y;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|