JSFiddle - React, Tailwind, and code Playground
HTML
<div id="spec" class="spec">
<!-- Piece specs go in here. Samples are shown below. Be careful: whitespace sensitive! -->
ABCD a
b Oo
c oo
d
E h
F efg hg GFE
GH f H
e
I IJK
J l L
LK kji kl
j
i
t
OP p QR rs
MN on ST q
m
W z
XY zxw yx Y
Z y w WXZ
</div>
<div class="spec">
<!-- These are the piece specs for COTO's C 72 answer. -->
" e m " T ) 6 }; ii 3" (. \nn {6" ]"0 i%s
i t p , & m 1 -] ar "\ f( t 8 e T
s )" .[ 0x ,f ai 0r
"
"" (t ["
)" 61 4+
</div>
<!-- Edit below at your own risk. -->
<div class="proto-tet">
<!-- I Pieces -->
<table class="tet tet-I tet-I0" rr="tet-I1" pat="[1,0],[2,0],[3,0]">
<tr><td>@11</td><td>@22</td><td>@33</td><td>@44</td></tr>
</table>
<table class="tet tet-I tet-I1" rr="tet-I2" pat="[0,1],[0,2],[0,3]">
<tr><td>@11</td></tr>
<tr><td>@22</td></tr>
<tr><td>@33</td></tr>
<tr><td>@44</td></tr>
</table>
<table class="tet tet-I tet-I2" rr="tet-I3" >
<tr><td>@40</td><td>@30</td><td>@20</td><td>@10</td></tr>
</table>
<table class="tet tet-I tet-I3" rr="tet-I0">
<tr><td>@40</td></tr>
<tr><td>@30</td></tr>
<tr><td>@20</td></tr>
<tr><td>@10</td></tr>
</table>
<!-- J Pieces -->
<table class="tet tet-J tet-J0" rr="tet-J1" pat="[0,1],[-1,2],[0,2]">
<tr><td></td><td>@11</td></tr>
<tr><td></td><td>@22</td></tr>
<tr><td>@33</td><td>@44</td></tr>
</table>
<table class="tet tet-J tet-J1" rr="tet-J2" pat="[0,1],[1,1],[2,1]">
<tr><td>@31</td><td></td><td></td></tr>
<tr><td>@42</td><td>@23</td><td>@14</td></tr>
</table>
<table class="tet tet-J tet-J2" rr="tet-J3" pat="[1,0],[0,1],[0,2]">
<tr><td>@41</td><td>@32</td></tr>
<tr><td>@23</td><td></td></tr>
<tr><td>@14</td><td></td></tr>
</table>
<table class="tet tet-J tet-J3" rr="tet-J0"...
CSS
.proto-tet, .spec {
display: none;
}
.tet-I { color: darkgreen; }
.tet-J { color: orangered; }
.tet-L { color: navy; }
.tet-T { color: darkred; }
.tet-O { color: darkcyan; }
.tet-S { color: darkviolet; }
.tet-Z { color: darkorange; }
body > .tet {
position: absolute;
cursor: move;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-collapse: collapse;
}
.tet td {
width: 18px;
height: 18px;
font: bold 14px "Courier New",monospace;
text-align: center;
vertical-align: middle;
padding: 0;
}
.error {
z-index: 1024;
position: absolute;
display: none;
color: red;
font-weight: bold;
background-color: white;
}
JavaScript
var G = {
eDrag: null,
isIgnoreClick: true,
iSpawn: 0
};
function parseSpec( s ) {
var P, S = s.split('\n').map( s_ => [...s_] );
var oPats = $('.proto-tet [pat]').get().map( e => {
return {
sPat: eval( '[' + $(e).attr('pat') + ']' ),
e: e,
block: function( P )
{ return [at( P ), ...this.sPat.map( P_ => at( [P[0]+P_[0],P[1]+P_[1]] ) )]; },
wipe: function( P_ )
{ this.sPat.forEach( P_ => wipe( [P[0]+P_[0],P[1]+P_[1]] ) ); },
match: function( P_ )
{ return !/\s/.test( this.block( P_ ).join('') ); }
};
} );
function first() {
var x_, y_;
y_ = S.findIndex( s_ => ((x_ = s_.findIndex( c_ => /\S/.test( c_ ) )) != -1) );
return y_ == -1 ? null : [x_,y_];
}
function at( P_ ) {
var x_ = P_[0],
y_ = P_[1];
return y_ >= 0 && y_ < S.length && x_ >= 0 && x_ < S[y_].length ? S[y_][x_] : ' ';
}
function wipe( P_ ) {
var x_ = P_[0],
y_ = P_[1];
if( y_ >= 0 && y_ < S.length && x_ >= 0 && x_ < S[y_].length )
S[y_][x_] = ' ';
}
window.oPats = oPats;
while( P = first() ) {
var oPat = oPats.find( o_ => o_.match( P ) );
if( !oPat ) {
orphan( at( P ) );
wipe( P );
continue;
}
createPiece( oPat.e, oPat.block( P ) );
wipe( P );
oPat.wipe( P );
}
}
function createPiece( e, sChars ) {
var sNormChars = [];
function ondown( eEvt ) {
var oPos = $(this).position();
G.isIgnoreClick = false;
G.eDrag = this;
G.iOffsets = [eEvt.clientX - oPos.left, eEvt.clientY - oPos.top];
}
function onclick() {
if( G.isIgnoreClick )
return;
var $E = $(this);
prep( $('.proto-tet .' + $E.attr('rr')), (_,s1_) => sNormChars[s1_-1], $E.css('left'), $E.css('top') );
$E.remove();
}
function prep( $R, fxRep, sLeft, sTop ) {
$R.clone().html( $R.html().replace( /@(\d)(\d)/g, fxRep ) ).
appendTo( 'body' ).
on( 'mousedown', ondown ).
click( onclick ).
css( { left: sLeft, top: sTop } );
}
prep( $(e), (_,s1_,s2_) => (sNormChars[s1_-1] = sChars[s2_-1]), (18 + (G.iSpawn%8)*18*4) +...