'''
Features used by AlphaGo, in order of importance.
Feature                 # Notes
Stone colour            3 Player stones; oppo. stones; empty  
Ones                    1 Constant plane of 1s
Turns since last move   8 How many turns since a move played
Liberties               8 Number of liberties
Capture size            8 How many opponent stones would be captured
Self-atari size         8 How many own stones would be captured
Liberties after move    8 Number of liberties after this move played
ladder capture          1 Whether a move is a successful ladder cap
Ladder escape           1 Whether a move is a successful ladder escape
Sensibleness            1 Whether a move is legal + doesn't fill own eye
Zeros                   1 Constant plane of 0s

All features with 8 planes are 1-hot encoded, with plane i marked with 1 
only if the feature was equal to i. Any features >= 8 would be marked as 8.
'''
import itertools
import re

import numpy as np
import go
RAW_BOARD_EXTRACTOR_RE = re.compile(r'[^BW.]+')

def make_onehot(feature, planes):
    onehot_features = np.zeros(feature.shape + (planes,))
    for i in range(planes - 1):
        onehot_features[feature == i+1, i] = 1
    onehot_features[feature >= planes, planes-1] = 1
    return onehot_features

class Feature(object):
    planes = 1
    
    @staticmethod
    def extract(position):
        return np.zeros([go.N, go.N, Feature.planes], dtype=np.float32)

class StoneColorFeature(Feature):
    planes = 3

    @staticmethod
    def extract(position):
        raw_board = RAW_BOARD_EXTRACTOR_RE.sub('', position.board).encode('ascii')
        board = np.frombuffer(raw_board, dtype=np.int8).reshape([go.N, go.N])
        features = np.zeros([go.N, go.N, 3], dtype=np.float32)
        features[board == ord('B'), 0] = 1
        features[board == ord('W'), 1] = 1
        features[board == ord('.'), 2] = 1
        return features

class LibertyFeature(Feature):
    '''
    From the AlphaGo paper: 
    Each integer feature value is split into multiple 19 × 19 planes of binary values (one-hot encoding). For example, separate binary feature planes are used to represent whether an intersection has 1 liberty, 2 liberties,..., >=8 liberties.
    '''
    planes = 8
    
    @staticmethod
    def extract(position):
        # the groups' coordinates are with respect to a padded board.
        padded_features = np.zeros([(go.W)**2], dtype=np.float32)
        for g in itertools.chain(*position.groups):
            padded_features[list(g.stones)] = len(g.liberties)

        # remove padding from representation
        features = padded_features.reshape([go.W, go.W])[1:, :-1]
        return make_onehot(features, LibertyFeature.planes)

@@ -1538,20 +1538,16 @@
     
-raw_
 board = 
 RAW_
@@ -1546,148 +1546,22 @@
 d = 
-RAW_BOARD_EXTRACTOR_RE.sub('', position.board).encode('ascii')%0A        board = np.frombuffer(raw_board, dtype=np.int8).reshape(%5Bgo.N, go.N%5D)
+position.board
 %0A   
@@ -1650,16 +1650,9 @@
  == 
-ord('B')
+1
 , 0%5D
@@ -1686,16 +1686,9 @@
  == 
-ord('W')
+0
 , 1%5D
@@ -1722,16 +1722,10 @@
  == 
-ord('.')
+-1
 , 2%5D
@@ -2150,85 +2150,8 @@
     
-# the groups' coordinates are with respect to a padded board.%0A        padded_
 feat
@@ -2171,17 +2171,18 @@
 os(%5B
-(go.W)**2
+go.N, go.N
 %5D, d
@@ -2266,39 +2266,12 @@
     
-padded_features%5Blist(g.stones)%5D
+libs
  = l
@@ -2286,25 +2286,25 @@
 erties)%0A
-%0A
+ 
         
 # remove
@@ -2299,110 +2299,64 @@
     
-# remove padding from representation%0A        features = padded_features.reshape(%5Bgo.W, go.W%5D)%5B1:, :-1%5D
+   for s in g.stones:%0A                features%5Bs%5D = libs
 %0A   

Fix features for numpy representation. Perf test confirmed as 30% faster