JSFiddle - React, Tailwind, and code Playground

by Marc Malignan

HTML

<div id="scrabble" ng-app="scrabble" ng-controller="scrabbleCtrl">
    
    <center>
        <button id="draw" ng-click="draw()">Draw</button>
        <button id="draw" ng-click="nextTurn()">Next turn</button>
    </center>
    
    <div><b>Turn :</b> Player {{turn}}</div>
    <div><b>Score :</b> {{players[0].score}} pts</div>
    
    <hand id="p2" class="clearfix" ng-class="{myturn: (turn==1)}">
        <tile data="t" draggable="{{(turn==t.owner)}}"
            ng-class="{draggable: (turn==t.owner)}"
            ng-repeat="t in players[1].hand track by $index"></tile>
    </hand>
    
    <div id="board" class="clearfix">
        <cell id="{{cellId($index+1)}}" ng-repeat="c in cells() track by $index" ></cell>
    </div>
    
    <hand id="p1" class="clearfix" ng-class="{myturn: (turn==0)}">
        <tile data="t" draggable="{{(turn==t.owner)}}"
            ng-class="{draggable: (turn==t.owner)}"
            ng-repeat="t in players[0].hand track by $index"></tile>
    </hand>
    
</div>

SCSS

$cell: 20px;
$border: $cell/10;

$bg: #f1ebd5;
$board: #efe9d3;
$red: #ec3330;
$pink: pink;
$blue: #1190d3;
$lightblue: #96cee9;

body {
    background: $bg;
    font-family: Arial;
}

center {
    margin-bottom: 10px;
}

.clearfix:after {
    content: '';
    display: block;
    clear: both;
}

#scrabble {
    cursor: default;
}

#board {
    margin: 0 auto;
    padding: $border 0 0 $border;
    width: (15*$cell)+(15*$border);
    background: white;
    border-radius: $cell/10;
    box-shadow: 0 2px 8px rgba(0,0,0,.3);
}

cell, tile {
    display: block;
    position: relative;
    width: $cell;
    height: $cell;
    line-height: $cell;
    text-align: center;
    border-radius: $cell/20;
}
cell {
    float: left;
    margin: 0 $border $border 0;
    background: $board;
}
tile {
    background: #eee;
    cursor: no-drop;
}

tile.draggable { cursor: -webkit-grab; }
tile.draggable:active { cursor: -webkit-grabbing; }

tile .pts {
    position: absolute;
    top: 0; right: 0;
    line-height: $cell/3;
    font-size: 40%;
}

hand {
    display: block;
    margin: 20px auto;
    padding: $border 0 $border $border;
    width: ($cell*15) + ($border*15);
    background: #333;
    border-radius: $cell/10;
}
hand tile {
    float: left;
    margin-right: $border;
}

hand.myturn {
    box-shadow: 0 0 0 2px limegreen;
}

#h8:before {
    content: '\2605';
    position: absolute;
    top: 0; left: 0; right: 0;
    line-height: $cell;
    font-size: $cell;
    text-align: center;
}

#a4, #a12,
#c7, #c9, 
#d1, #d8, #d15,
#g3, #g7, #g9, #g13,
#h4, #h12,
#i3, #i7, #i9, #i13,
#l1, #l8, #l15,
#m7, #m9,
#o4, #o12 { background: $lightblue; }

#b6, #b10,
#f2, #f6, #f10, #f14,
#j2, #j6, #j10, #j14, 
#n6, #n10 { background: $blue; }

#b2, #c3, #d4, #e5,
#b14, #c13, #d12, #e11,
#h8,
#n2, #m3, #l4, #k5,
#n14, #m13, #l12, #k11,{ background: pink; }

#a1, #a8, #a15,
#h1, #h15,
#o1, #o8, #o15 { background: $red }

JavaScript

var app = angular.module('scrabble', []);

app.directive('cell', function($rootScope) {
    return {
        restrict: 'E',
        link: function(scope, el, attrs, controller) {
            
            scope.tile = null;
            
            el.bind("dragover", function(e) {
                e.preventDefault();
                return false;
            });
            
            el.bind("drop", function(e) {
                e.preventDefault();

                scope.tile = $rootScope.dragged;
                
                $rootScope.word[el.attr('id')] = scope.tile;
                $rootScope.$broadcast("dropBoard", scope.tile);
            });
        },
        template: '<tile data="tile" ng-if="tile" draggable="{{(turn==tile.owner)}}" ng-class="{draggable: (turn==tile.owner)}"></tile>'
    }
});

app.directive('hand', function($rootScope) {
    return {
        restrict: 'E',
        link: function(scope, el, attrs, controller) {
            
            el.bind("dragover", function(e) {
                if('p'+($rootScope.turn+1) == el.attr('id')) {
                    e.preventDefault();
                    return false;
                }
            });
            
            el.bind("drop", function(e) {
                e.preventDefault();
                $rootScope.$broadcast("dropHand", $rootScope.dragged);
            });
        }
    }
});

app.directive('tile', function($rootScope) {
    return {
        restrict: 'E',
        scope: { data: '=' },
        link: function(scope, el, attrs, controller) {
            
            el.bind("dragstart", function(e) {
                var parent = el.parent();
                parent.scope().tile = null;
                delete $rootScope.word[parent.attr('id')];
                $rootScope.$broadcast("dragStart", scope.data);
            });
            el.bind("dragend", function(e) {
                e.preventDefault();
                $rootScope.$broadcast("dragEnd");
            });
        },
 ...