├── .gitignore ├── .gitmodules ├── .travis.yml ├── AlphaGo ├── __init__.py ├── ai.py ├── go.py ├── mcts.py ├── models │ ├── SGD_exponential_decay.py │ ├── __init__.py │ ├── deep_policy.py │ ├── policy.py │ ├── shallow_policy.py │ └── value.py └── training │ ├── gen_value_positions.py │ ├── train_rl.py │ ├── train_supervised.py │ └── train_value.py ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── data ├── self_play │ └── s_a_z_tuples_here_format_TBD ├── trained_models │ └── h5_files_here_by_hyperparamer_UID └── utils │ ├── __init__.py │ ├── game_converter.py │ ├── game_logic.py │ └── sgflib │ ├── README.txt │ ├── __init__.py │ ├── lgpl.txt │ ├── sgflib.py │ └── typelib.py ├── interface ├── opponents │ └── pachi │ │ └── pachi.py └── server │ ├── go.html │ ├── goServer.py │ └── wgo │ ├── basicplayer.commentbox.js │ ├── basicplayer.component.js │ ├── basicplayer.control.js │ ├── basicplayer.infobox.js │ ├── basicplayer.js │ ├── kifu.js │ ├── player.editable.js │ ├── player.js │ ├── player.permalink.js │ ├── scoremode.js │ ├── sgfparser.js │ ├── wgo.js │ ├── wgo.min.js │ ├── wgo.player.css │ ├── wgo.player.min.js │ └── wood1.jpg ├── requirements.txt └── tests ├── __init__.py ├── test_gamestate.py └── test_liberties.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.npy 3 | *.h5 4 | *.sgf 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "interface/opponents/pachi/pachi"] 2 | path = interface/opponents/pachi/pachi 3 | url = https://github.com/pasky/pachi 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | languate: python 2 | python: 3 | - "2.7" 4 | # Setup anaconda 5 | before_install: 6 | - wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh 7 | - chmod +x miniconda.sh 8 | - ./miniconda.sh -b 9 | - export PATH=/home/travis/miniconda2/bin:$PATH 10 | - conda update --yes conda 11 | # The next couple lines fix a crash with multiprocessing on Travis and are not specific to using Miniconda 12 | - sudo rm -rf /dev/shm 13 | - sudo ln -s /run/shm /dev/shm 14 | # Install packages 15 | install: 16 | - conda install --yes python=2.7 numpy scipy matplotlib pandas pytest h5py 17 | - pip install --user --no-deps Keras==0.3.1 Theano==0.7.0 18 | # run unit tests 19 | script: python -m unittest discover -------------------------------------------------------------------------------- /AlphaGo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/AlphaGo/__init__.py -------------------------------------------------------------------------------- /AlphaGo/ai.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/AlphaGo/ai.py -------------------------------------------------------------------------------- /AlphaGo/go.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | WHITE = -1 4 | BLACK = +1 5 | EMPTY = 0 6 | 7 | class GameState(object): 8 | """State of a game of Go and some basic functions to interact with it 9 | """ 10 | 11 | def __init__(self, size=19): 12 | self.board = np.zeros((size, size)) 13 | self.board.fill(EMPTY) 14 | self.size = size 15 | self.turns_played = 0 16 | self.current_player = BLACK 17 | 18 | def liberty_count(self, position): 19 | """Count liberty of a single position (maxium = 4). 20 | 21 | Keyword arguments: 22 | position -- a tuple of (x, y) 23 | x being the column index of the position we want to calculate the liberty 24 | y being the row index of the position we want to calculate the liberty 25 | 26 | Return: 27 | q -- A interger in [0, 4]. The count of liberty of the input single position 28 | """ 29 | return len(self.liberty_pos(position)) 30 | 31 | def liberty_pos(self, position): 32 | """Record the liberty position of a single position. 33 | 34 | Keyword arguments: 35 | position -- a tuple of (x, y) 36 | x being the column index of the position we want to calculate the liberty 37 | y being the row index of the position we want to calculate the liberty 38 | 39 | Return: 40 | pos -- Return a list of tuples consist of (x, y)s which are the liberty positions on the input single position. len(pos) <= 4 41 | """ 42 | (x, y) = position 43 | pos=[] 44 | if x+1 < self.size and self.board[x+1][y] == EMPTY: 45 | pos.append((x+1, y)) 46 | if y+1 < self.size and self.board[x][y+1] == EMPTY: 47 | pos.append((x, y+1)) 48 | if x - 1 >= 0 and self.board[x-1][y] == EMPTY: 49 | pos.append((x-1, y)) 50 | if y - 1 >= 0 and self.board[x][y-1] == EMPTY: 51 | pos.append((x, y-1)) 52 | return pos 53 | 54 | def get_neighbor(self, position): 55 | """An auxiliary function for curr_liberties. This function looks around locally in 4 directions. That is, we just pick one position and look to see if there are same-color neighbors around it. 56 | 57 | Keyword arguments: 58 | position -- a tuple of (x, y) 59 | x being the column index of the position in consideration 60 | y being the row index of the posisiton in consideration 61 | 62 | Return: 63 | neighbor -- Return a list of tuples consist of (x, y)s which are the same-color neighbors of the input single position. len(neighbor_set) <= 4 64 | """ 65 | (x, y) = position 66 | neighbor_set=[] 67 | if y+1 < self.size and self.board[x][y] == self.board[x][y+1]: 68 | neighbor_set.append((x,y+1)) 69 | if x+1 < self.size and self.board[x][y] == self.board[x+1][y]: 70 | neighbor_set.append((x+1,y)) 71 | if x-1 >= 0 and self.board[x][y] == self.board[x-1][y]: 72 | neighbor_set.append((x-1,y)) 73 | if y-1 >= 0 and self.board[x][y] == self.board[x][y-1]: 74 | neighbor_set.append((x,y-1)) 75 | return neighbor_set 76 | 77 | def visit_neighbor(self, position): 78 | """An auxiliary function for curr_liberties. This function perform the visiting process to identify a connected group of the same color 79 | 80 | Keyword arguments: 81 | position -- a tuple of (x, y) 82 | x being the column index of the starting position of the search 83 | y being the row index of the starting position of the search 84 | 85 | Return: 86 | neighbor_set -- Return a set of tuples consist of (x, y)s which are the same-color cluster which contains the input single position. len(neighbor_set) is size of the cluster, can be large. 87 | """ 88 | (x, y) = position 89 | # handle case where there is no piece at (x,y) 90 | if self.board[x][y] == EMPTY: 91 | return set() 92 | # A list for record the places we visited in the process 93 | # default to the starting position to handle the case where there are no neighbors (group size is 1) 94 | visited=[(x,y)] 95 | # A list for the the places we still want to visit 96 | to_visit=self.get_neighbor((x,y)) 97 | while len(to_visit)!=0: 98 | for n in to_visit: 99 | # append serve as the actual visit 100 | visited.append(n) 101 | # take off the places already visited from the wish list 102 | to_visit.remove(n) 103 | # With the cluster we have now, we look around even further 104 | for v in visited: 105 | # we try to look for same-color neighbors for each one which we already visited 106 | for n in self.get_neighbor(v): 107 | # we don't need to consider the places we already visited when we're looking 108 | if n not in visited: 109 | to_visit.append(n) 110 | 111 | neighbor_set=set(visited) 112 | return neighbor_set 113 | 114 | def update_current_liberties(self): 115 | """Calculate the liberty values of the whole board 116 | 117 | Keyword arguments: 118 | None. We just need the board itself. 119 | 120 | Return: 121 | A matrix self.size * self.size, with entries of the liberty number of each position on the board. 122 | Empty spaces have liberty 0. Instead of the single stone liberty, we consider the liberty of the 123 | group/cluster of the same color the position is in. 124 | """ 125 | 126 | curr_liberties = np.ones((self.size, self.size)) * (-1) 127 | 128 | for x in range(0, self.size): 129 | for y in range(0, self.size): 130 | 131 | if self.board[x][y] == EMPTY: 132 | continue 133 | 134 | # get the members in the cluster and then calculate their liberty positions 135 | lib_set = set() 136 | neighbors = self.visit_neighbor((x,y)) 137 | for n in neighbors: 138 | lib_set |= set(self.liberty_pos(n)) 139 | 140 | curr_liberties[x][y] = len(lib_set) 141 | return curr_liberties 142 | 143 | def update_future_liberties(self, action): 144 | """Calculate the liberty values of the whole board after we make a new move 145 | 146 | Keyword arguments: 147 | action -- a tuple of (x, y) 148 | x being the column index of the position of the future move 149 | y being the row index of the position of the future move 150 | 151 | Return: 152 | A matrix self.size * self.size, with entries of the liberty number of each position on the board, after the future move. 153 | """ 154 | future = self.copy() 155 | future.do_move(action) 156 | future_liberties = future.update_current_liberties() 157 | 158 | return future_liberties 159 | 160 | def copy(self): 161 | """get a copy of this Game state 162 | """ 163 | other = GameState(self.size) 164 | other.board = self.board.copy() 165 | other.turns_played = self.turns_played 166 | other.current_player = self.current_player 167 | return other 168 | 169 | def is_legal(self, action): 170 | """determine if the given action (x,y tuple) is a legal move 171 | """ 172 | (x,y) = action 173 | empty = self.board[x][y] == EMPTY 174 | on_board = x >= 0 and y >= 0 and x < self.size and y < self.size 175 | suicide = False # todo 176 | ko = False # todo 177 | return on_board and (not suicide) and (not ko) #and empty 178 | 179 | def do_move(self, action): 180 | """Play current_player's color at (x,y) 181 | 182 | If it is a legal move, current_player switches to the other player 183 | If not, an IllegalMove exception is raised 184 | """ 185 | (x,y) = action 186 | if self.is_legal((x,y)): 187 | self.board[x][y] = self.current_player 188 | self.current_player = -self.current_player 189 | self.turns_played += 1 190 | else: 191 | raise IllegalMove(str((x,y))) 192 | 193 | def symmetries(self): 194 | """returns a list of 8 GameState objects: 195 | all reflections and rotations of the current board 196 | 197 | does not check for duplicates 198 | """ 199 | copies = [self.copy() for i in range(8)] 200 | # copies[0] is the original. 201 | # rotate CCW 90 202 | copies[1].board = np.rot90(self.board,1) 203 | # rotate 180 204 | copies[2].board = np.rot90(self.board,2) 205 | # rotate CCW 270 206 | copies[3].board = np.rot90(self.board,3) 207 | # mirror left-right 208 | copies[4].board = np.fliplr(self.board) 209 | # mirror up-down 210 | copies[5].board = np.flipud(self.board) 211 | # mirror \ diagonal 212 | copies[6].board = np.transpose(self.board) 213 | # mirror / diagonal (equivalently: rotate 90 CCW then flip LR) 214 | copies[7].board = np.fliplr(copies[1].board) 215 | return copies 216 | 217 | def from_sgf(self, sgf_string): 218 | raise NotImplementedError() 219 | 220 | def to_sgf(self, sgf_string): 221 | raise NotImplementedError() 222 | 223 | 224 | class IllegalMove(Exception): 225 | pass -------------------------------------------------------------------------------- /AlphaGo/mcts.py: -------------------------------------------------------------------------------- 1 | class MCTS(object): 2 | pass 3 | 4 | class ParallelMCTS(MCTS): 5 | pass -------------------------------------------------------------------------------- /AlphaGo/models/SGD_exponential_decay.py: -------------------------------------------------------------------------------- 1 | from keras.optimizers import SGD 2 | from keras import backend as K 3 | import numpy as np 4 | 5 | class SGD_exponential_decay(SGD): 6 | '''Stochastic gradient descent. Same as built in SGD module, except 7 | the learning rate decreases as a recurrent linear function of decay, 8 | i.e., it doesn't depend on self.iterations. 9 | ''' 10 | def __init__(self, lr=0.01, momentum=0., decay=0., nesterov=False, 11 | *args, **kwargs): 12 | super(SGD_exponential_decay, self).__init__(**kwargs) 13 | self.__dict__.update(locals()) 14 | self.iterations = K.variable(0.) 15 | self.lr = K.variable(lr) 16 | self.momentum = K.variable(momentum) 17 | self.decay = K.variable(decay) 18 | 19 | def get_updates(self, params, constraints, loss): 20 | grads = self.get_gradients(loss, params) 21 | ### THE UPDATED CALCULATION ### 22 | lr = self.lr * (1.0 / (1.0 + self.decay)) 23 | self.updates = [(self.iterations, self.iterations + 1.)] 24 | for p, g, c in zip(params, grads, constraints): 25 | m = K.variable(np.zeros(K.get_value(p).shape)) # momentum 26 | v = self.momentum * m - lr * g # velocity 27 | self.updates.append((m, v)) 28 | 29 | if self.nesterov: 30 | new_p = p + self.momentum * v - lr * g 31 | else: 32 | new_p = p + v 33 | 34 | self.updates.append((p, c(new_p))) # apply constraints 35 | return self.updates 36 | -------------------------------------------------------------------------------- /AlphaGo/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/AlphaGo/models/__init__.py -------------------------------------------------------------------------------- /AlphaGo/models/deep_policy.py: -------------------------------------------------------------------------------- 1 | from keras.models import Sequential 2 | from keras.layers import convolutional 3 | from keras.layers.core import Activation, Reshape 4 | from SGD_exponential_decay import SGD_exponential_decay as SGD 5 | 6 | ### Parameters obtained from paper ### 7 | K = 152 # depth of convolutional layers 8 | LEARNING_RATE = .003 # initial learning rate 9 | DECAY = 8.664339379294006e-08 # rate of exponential learning_rate decay 10 | 11 | class deep_policy_trainer: 12 | def __init__(self): 13 | self.model = Sequential() 14 | self.model.add(convolutional.Convolution2D(input_shape=(48, 19, 19), nb_filter=K, nb_row=5, nb_col=5, 15 | init='uniform', activation='relu', border_mode='same')) 16 | for i in range(2,13): 17 | self.model.add(convolutional.Convolution2D(nb_filter=K, nb_row=3, nb_col=3, 18 | init='uniform', activation='relu', border_mode='same')) 19 | self.model.add(convolutional.Convolution2D(nb_filter=1, nb_row=1, nb_col=1, 20 | init='uniform', border_mode='same')) 21 | self.model.add(Reshape((19,19))) 22 | self.model.add(Activation('softmax')) 23 | 24 | sgd = SGD(lr=LEARNING_RATE, decay=DECAY) 25 | self.model.compile(loss='binary_crossentropy', optimizer=sgd) 26 | 27 | def get_samples(self): 28 | # TODO non-terminating loop that yields training samples drawn uniformly at random 29 | pass 30 | 31 | def train(self): 32 | # TODO use self.model.fit_generator to train from data source 33 | pass 34 | 35 | if __name__ == '__main__': 36 | trainer = deep_policy_trainer() 37 | # TODO command line routine 38 | -------------------------------------------------------------------------------- /AlphaGo/models/policy.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/AlphaGo/models/policy.py -------------------------------------------------------------------------------- /AlphaGo/models/shallow_policy.py: -------------------------------------------------------------------------------- 1 | # TODO 2 | -------------------------------------------------------------------------------- /AlphaGo/models/value.py: -------------------------------------------------------------------------------- 1 | from keras.models import Sequential 2 | from keras.layers import convolutional 3 | from keras.layers.core import Dense, Flatten 4 | from SGD_exponential_decay import SGD_exponential_decay as SGD 5 | 6 | ### Parameters obtained from paper ### 7 | K = 152 # depth of convolutional layers 8 | LEARNING_RATE = .003 # initial learning rate 9 | DECAY = 8.664339379294006e-08 # rate of exponential learning_rate decay 10 | 11 | class value_trainer: 12 | def __init__(self): 13 | self.model = Sequential() 14 | self.model.add(convolutional.Convolution2D(input_shape=(49, 19, 19), nb_filter=K, nb_row=5, nb_col=5, 15 | init='uniform', activation='relu', border_mode='same')) 16 | for i in range(2,13): 17 | self.model.add(convolutional.Convolution2D(nb_filter=K, nb_row=3, nb_col=3, 18 | init='uniform', activation='relu', border_mode='same')) 19 | 20 | self.model.add(convolutional.Convolution2D(nb_filter=1, nb_row=1, nb_col=1, 21 | init='uniform', activation='linear', border_mode='same')) 22 | self.model.add(Flatten()) 23 | self.model.add(Dense(256,init='uniform')) 24 | self.model.add(Dense(1,init='uniform',activation="tanh")) 25 | 26 | sgd = SGD(lr=LEARNING_RATE, decay=DECAY) 27 | self.model.compile(loss='mean_squared_error', optimizer=sgd) 28 | 29 | def get_samples(self): 30 | # TODO non-terminating loop that draws training samples uniformly at random 31 | pass 32 | 33 | def train(self): 34 | # TODO use self.model.fit_generator to train from data source 35 | pass 36 | 37 | if __name__ == '__main__': 38 | trainer = value_trainer() 39 | # TODO command line instantiation 40 | -------------------------------------------------------------------------------- /AlphaGo/training/gen_value_positions.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/AlphaGo/training/gen_value_positions.py -------------------------------------------------------------------------------- /AlphaGo/training/train_rl.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/AlphaGo/training/train_rl.py -------------------------------------------------------------------------------- /AlphaGo/training/train_supervised.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/AlphaGo/training/train_supervised.py -------------------------------------------------------------------------------- /AlphaGo/training/train_value.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/AlphaGo/training/train_value.py -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Git guide for this project 2 | 3 | Get familiar with git's collaboration model - there are 4 | [plenty](http://rogerdudler.github.io/git-guide/) 5 | [of](https://guides.github.com/introduction/flow/) 6 | [resources](https://www.atlassian.com/git/tutorials/syncing) 7 | for this! 8 | 9 | Fork this repository, and push all your changes to your copy. Make sure your branch is up to date with the central repository before making a pull request. [Git-scm](https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows#Integration-Manager-Workflow) describes this model well. 10 | 11 | Follow these guidelines in particular: 12 | 13 | 1. keep `upstream/master` functional 14 | 1. write useful commit messages 15 | 1. `commit --amend` or `rebase` to avoid publishing a series of "oops" commits (better done on your own branch, not `master`) ([read this](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History)) 16 | 1. ..but don't modify published history 17 | 1. prefer `rebase master` to `merge master`, again for the sake of keeping histories clean. Don't do this if you're not totally comfortable with how `rebase` works. 18 | 1. track issues via GitHub's tools 19 | 20 | ## Coding guide 21 | 22 | We are using Python 2.7. I recommend using `virtualenv` to set up an environment; [requirements.txt](requirements.txt) contains the necessary python modules. Beyond that, follow these guidelines: 23 | 24 | 1. remember that ["code is read more often than it is written"](https://www.python.org/dev/peps/pep-0008) 25 | 1. avoid premature optimization. instead, be pedantic and clear with code and we will make targeted optimizations later using a profiler 26 | 1. write [tests](https://docs.python.org/2/library/unittest.html). These are scripts that essentially try to break your own code and make sure your classes and functions can handle what is thrown at them 27 | 1. [document](http://epydoc.sourceforge.net/docstrings.html) every class and function, comment liberally -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 University of Rochester 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AlphaGoReplication 2 | 3 | A replication of DeepMind's 2016 Nature publication, "Mastering the game of Go with deep neural networks and tree search," details of which can be found [on their website](http://deepmind.com/alpha-go.html). 4 | 5 | [![Build Status](https://travis-ci.org/Rochester-NRT/AlphaGo.svg?branch=master)](https://travis-ci.org/Rochester-NRT/AlphaGo) 6 | -------------------------------------------------------------------------------- /data/self_play/s_a_z_tuples_here_format_TBD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/data/self_play/s_a_z_tuples_here_format_TBD -------------------------------------------------------------------------------- /data/trained_models/h5_files_here_by_hyperparamer_UID: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/data/trained_models/h5_files_here_by_hyperparamer_UID -------------------------------------------------------------------------------- /data/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/data/utils/__init__.py -------------------------------------------------------------------------------- /data/utils/game_converter.py: -------------------------------------------------------------------------------- 1 | import os, argparse 2 | import numpy as np 3 | import game_logic as gl 4 | from sgflib.sgflib import SGFParser, GameTreeEndError 5 | 6 | class game_converter: 7 | def __init__(self,target_format="deep"): 8 | self.index_at = {'a':0,'b':1,'c':2,'d':3, 9 | 'e':4,'f':5,'g':6,'h':7, 10 | 'i':8,'j':9,'k':10,'l':11, 11 | 'm':12,'n':13,'o':14,'p':15, 12 | 'q':16,'r':17,'s':18} 13 | self.target_format = target_format # todo: implement this 14 | 15 | # convert move into board indices 16 | def parse_raw_move(self,raw_move): 17 | pos = list(str(raw_move)[3:5]) 18 | col = self.index_at[pos[0]] 19 | row = self.index_at[pos[1]] 20 | move = {'col':col,'row':row} 21 | return move 22 | 23 | # convert indices into 19x19 training label 24 | def encode_label(self,move): 25 | # convert move to one-hot encoding 26 | one_hot = np.zeros((19,19),dtype=bool) 27 | one_hot[move['col']][move['row']] = 1 28 | return one_hot 29 | 30 | # prepare training sample 31 | def append_state(self,states,move): 32 | if len(states) is not 0: 33 | # copy last board state 34 | state = np.copy(states[-1]) 35 | states.append(state) 36 | # relativise it to current player 37 | state[0:2] = state[0:2][::-1] 38 | else: # create board from scratch 39 | state = np.zeros((48,19,19),dtype=bool) 40 | # convert 3rd slice to ones because all board positions are empty 41 | # convert 4th slice to ones because it's a constant plane of ones 42 | # convert 5th slice to ones because no moves have been played yet 43 | state[2:5] = ~state[2:5] 44 | # add two states: 1 empty board and 1 in which we place 1st move 45 | states.append(state) 46 | state = np.copy(state) 47 | states.append(state) 48 | 49 | # perform move 50 | state[0][move['col']][move['row']] = 1 51 | state[2][move['col']][move['row']] = 0 52 | 53 | # compute feature slices based on game logic 54 | gl.check_for_capture(state[0:2]) 55 | gl.update_move_ages(state[4:12],move) 56 | gl.update_current_liberties(state[0:2],state[12:20]) 57 | gl.update_capture_sizes(state[0:2],state[20:29]) 58 | gl.update_self_atari_sizes(state[0:2],state[29:37]) 59 | gl.update_future_liberties(state[0:2],state[37:44]) 60 | gl.update_ladder_captures(state[0:2],state[44]) 61 | gl.update_ladder_escapes(state[0:2],state[45]) 62 | gl.update_sensibleness(state[0:2],state[46]) 63 | 64 | # convert full game into training samples 65 | def convert_game(self,file_name): 66 | with open(file_name,'r') as file_object: 67 | sgf_object = SGFParser(file_object.read()) 68 | c = sgf_object.parse().cursor() 69 | states = [] 70 | actions = [] 71 | while True: 72 | try: 73 | move = self.parse_raw_move(c.next()) 74 | actions.append(self.encode_label(move)) 75 | self.append_state(states,move) 76 | except GameTreeEndError: 77 | # remove last board state since it has no label 78 | states = states[0:-1] 79 | break 80 | return zip(states, actions) 81 | 82 | # lazily convert folder of games into training samples 83 | def batch_convert(self,folder_path): 84 | file_names = os.listdir(folder_path) 85 | for file_name in file_names: 86 | print file_name 87 | training_samples = self.convert_game(os.path.join(folder_path,file_name)) 88 | for sample in training_samples: 89 | yield sample 90 | 91 | if __name__ == '__main__': 92 | parser = argparse.ArgumentParser(description='Prepare a folder of Go game files for training our neural network model.') 93 | parser.add_argument("infolder", help="Relative path to folder containing games") 94 | parser.add_argument("outfolder", help="Relative path to target folder. Will be created if it does not exist.") 95 | parser.add_argument("-t","--target_format", help="One of: 'deep', 'shallow', or 'value'. Defaults to 'deep'") 96 | args = parser.parse_args() 97 | 98 | if not args.target_format: target_format = "deep" 99 | elif any([args.target_format == t for t in ["deep","shallow","value"]]): 100 | target_format = args.target_format 101 | else: raise ValueError("Unrecognized target format") 102 | 103 | converter = game_converter(target_format) 104 | 105 | for state,action in converter.batch_convert(args.infolder): 106 | # serialize and write deez guise to db 107 | pass 108 | -------------------------------------------------------------------------------- /data/utils/game_logic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | ''' These methods implement the updates required 4 | to prepare the features of each training tensor''' 5 | 6 | # @param ages: 8x19x19 boolean: 7 | ### An index of a slice is 1 iff a move is that old. 8 | # @param move: dict containing the row,col index of the most recent move. 9 | def update_move_ages(ages,move): 10 | # final slice collects all moves older than 6 turns 11 | ages[7] = np.logical_or(ages[6], ages[7]) 12 | # intermediate slices get shifted up 13 | ages[6] = ages[5] 14 | ages[5] = ages[4] 15 | ages[4] = ages[3] 16 | ages[3] = ages[2] 17 | ages[2] = ages[1] 18 | # youngest slice steals a 1 from the unplayed pool 19 | ages[1] = np.zeros((19,19),dtype=bool) 20 | ages[1][move['row']][move['col']] = 1 21 | ages[0][move['row']][move['col']] = 0 22 | 23 | # @param stones: 3x19x19 boolean: 24 | ### The first slice has a 1 at an index if the current player has a stone there. 25 | ### The second slice has a 1 at an index if the current player's opponent has a stone there. 26 | ### The third slice has a 1 at an index if neither player has a stone there. 27 | def check_for_capture(stones): 28 | pass 29 | 30 | # @param stones: 19x19 board consist of [0,1,2] denote [empty, black, white] 31 | # Output: curr_liberties: 8x19x19 boolean: 32 | ### An index of a slice is 1 iff the position has that many liberties. 33 | def update_current_liberties(stones): 34 | # Function to find the liberty of one stone 35 | def liberty_count(i, j): 36 | q=0 #liberty count 37 | if stones[i+1][j] == 0: 38 | q = q + 1 39 | if stones[i][j+1] == 0: 40 | q = q + 1 41 | if i-1 > 0 and stones[i-1][j] == 0: 42 | q = q + 1 43 | if j -1 > 0 and stones[i][j -1 ] == 0: 44 | q = q + 1 45 | return q 46 | # Record the liberty position 47 | def liberty_pos(i, j): 48 | pos=[] 49 | if stones[i+1][j] == 0: 50 | pos.append([i+1, j]) 51 | if stones[i][j+1] == 0: 52 | pos.append([i, j+1]) 53 | if i - 1 >= 0 and stones[i-1][j] == 0: 54 | pos.append([i-1, j]) 55 | if j - 1 >= 0 and stones[i][j-1] == 0: 56 | pos.append([i, j-1]) 57 | return pos 58 | 59 | # Scanning through the board 60 | lib_considered=[] 61 | for i in range(0, 19): 62 | for j in range(0, 19): 63 | if [i, j] == [x for x in lib_considered]: 64 | continue 65 | 66 | # The first position picked 67 | lib_considered.append(i, j) 68 | lib_set = [i, j] 69 | lib_c = liberty_count(i, j) 70 | lib_set.append(liberty_pos(i, j) 71 | 72 | # Scanning through 4 directions to find the same color cluster 73 | while stone[i][j] == stone[i][j+1]: 74 | lib_set.append(liberty_pos(i, j+1)) 75 | lib_c = lib_c + liberty_count(i, j+1) 76 | j = j + 1 77 | 78 | while stone[i][j] == stone[i+1][j]: 79 | lib_set.append(liberty_pos(i+1, j)) 80 | lib_c = lib_c + liberty_count(i+1, j) 81 | i = i + 1 82 | 83 | while i - 1 >= 0 and stone[i][j] == stone[i-1][j]: 84 | lib_set.append(liberty_pos(i-1, j) 85 | lib_c = lib_c + liberty_count(i-1, j) 86 | i = i - 1 87 | 88 | while j - 1 >= 0 and stone[i][j] == stone[i][j-1]: 89 | lib_set.append(liberty_pos(i, j-1)) 90 | lib_c = lib_c + liberty_count(i, j-1) 91 | j = j - 1 92 | 93 | # Combine the liberty position of the cluster found 94 | lib_set = set(lib_set) 95 | 96 | # Update onehot encoding rectangular prisim 97 | if lib_c > 0 and lib_c < 8: 98 | for pos in lib_set: 99 | curr_liberties[lib_c-1][pos[0]][pos[1]] = 1 100 | elif lib_c >= 8: 101 | for pos in lib_set: 102 | curr_liberties[7][pos[0]][pos[1]] = 1 103 | 104 | return curr_liberties 105 | 106 | # @param capture_sizes: 8x19x19 boolean: 107 | ### An index of a slice is 1 iff a move there would capture that many opponents. 108 | def update_capture_sizes(stones,capture_sizes): 109 | pass 110 | 111 | # @param self_ataris: 8x19x19 boolean: 112 | ### An index of a slice is 1 iff the playing a move there would capture that many of player's own stones. 113 | def update_self_atari_sizes(stones,self_ataris): 114 | pass 115 | 116 | # @param stones: 19x19 board consist of [0,1,2] denote [empty, black, white] 117 | # @param move: dict containing the row,col index of the most recent move. 118 | # Output: future_liberties: 8x19x19 boolean: 119 | ### An index of a slice is 1 iff playing a move there would yield that many liberties. 120 | 121 | def update_future_liberties(stones, move): 122 | # very similar to curr_liberties, only we do not scan the whole board this time 123 | # Only one cluster which contains the new move is considered 124 | i=move['row'] 125 | j=move['col'] 126 | 127 | lib_set = [i, j] 128 | lib_c = liberty_count(i, j) 129 | lib_set.append(liberty_pos(i, j) 130 | 131 | while stone[i][j] == stone[i][j+1]: 132 | lib_set.append(liberty_pos(i, j+1)) 133 | lib_c = lib_c + liberty_count(i, j+1) 134 | j = j + 1 135 | 136 | while stone[i][j] == stone[i+1][j]: 137 | lib_set.append(liberty_pos(i+1, j)) 138 | lib_c = lib_c + liberty_count(i+1, j) 139 | i = i + 1 140 | 141 | while i - 1 >= 0 and stone[i][j] == stone[i-1][j]: 142 | lib_set.append(liberty_pos(i-1, j) 143 | lib_c = lib_c + liberty_count(i-1, j) 144 | i = i - 1 145 | 146 | while j - 1 >= 0 and stone[i][j] == stone[i][j-1]: 147 | lib_set.append(liberty_pos(i, j-1)) 148 | lib_c = lib_c + liberty_count(i, j-1) 149 | j = j - 1 150 | 151 | lib_set = set(lib_set) 152 | 153 | future_liberties = curr_liberties(stones) 154 | # read the old data, and then update 155 | if lib_c > 0 and lib_c < 8: 156 | for pos in lib_set: 157 | future_liberties[lib_c-1][pos[0]][pos[1]] = 1 158 | elif lib_c >= 8: 159 | for pos in lib_set: 160 | future_liberties[7][pos[0]][pos[1]] = 1 161 | 162 | return future_liberties 163 | 164 | 165 | # @param ladder_captures: 19x19 boolean: 166 | ### An index is 1 iff playing a move there would be a successful ladder capture. 167 | def update_ladder_captures(stones,ladder_captures): 168 | pass 169 | 170 | # @param ladder_escapes: 19x19 boolean: 171 | ### An index is 1 iff playing a move there would be a successful ladder escape. 172 | def update_ladder_escapes(stones,ladder_escapes): 173 | pass 174 | 175 | # @param sensibleness: 19x19 boolean: 176 | ### An index is 1 iff a move is legal and does not fill its own eyes. 177 | def update_sensibleness(stones,sensibleness): 178 | pass 179 | -------------------------------------------------------------------------------- /data/utils/sgflib/README.txt: -------------------------------------------------------------------------------- 1 | ========================================== 2 | Go Tools: Smart Game Format Parser Library 3 | ========================================== 4 | 5 | Description 6 | =========== 7 | 8 | For Python programmers, sgflib.py is a module containing a parser and classes 9 | for SGF, the Smart Game Format. Also included is the module typelib.py, which 10 | emulates Python built-in data types as classes. 11 | 12 | Installation 13 | ============ 14 | 15 | Please note that sgflib.py and typelib.py are included as part of the 16 | sgfsummary.py download, so you need not download them separately. Although these 17 | modules are only useful to Python programmers, if you're not one, I encourage 18 | you to take a look. Python is a cool programming language. 19 | 20 | You'll need the Python language itself, freely available from 21 | [[http://www.python.org]]. 22 | 23 | The sgflib.tgz (tarred, gzipped) archive contains the following: 24 | 25 | - sgflib.py -- SGF Parser Library. Put this in a folder on Python's path. 26 | 27 | - typelib.py -- Type Class Library. Put this in a folder on Python's path. 28 | 29 | - README.txt -- Installation instructions (the file you're reading now). 30 | 31 | - lgpl.txt -- The GNU Lesser General Public License; applies to sgflib.py. 32 | 33 | I have only tested this code on my system, MacOS 8.6, running Python 1.5.2. I 34 | did try to write it to be platform-independent. If you have any trouble running 35 | it, find (fix?) any bugs, or add any features, please contact me. 36 | 37 | MacOS users: please see the Macintosh Python Notes and the Macintosh SGF Notes 38 | at [[http://gotools.sourceforge.net]]. 39 | 40 | To Do 41 | ===== 42 | 43 | There's nothing on my list for sgflib.py right now. 44 | 45 | typelib.py: 46 | 47 | - Implement 'Function'? 'File'? (Have to come up with a good reason first ;-) 48 | 49 | Have any suggestions? Want to help? Let me know! 50 | 51 | Contact 52 | ======= 53 | 54 | Project administrator: David Goodger [[mailto:dgoodger@bigfoot.com]] 55 | Go Tools Project website: [[http://gotools.sourceforge.net]] 56 | -------------------------------------------------------------------------------- /data/utils/sgflib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/data/utils/sgflib/__init__.py -------------------------------------------------------------------------------- /data/utils/sgflib/sgflib.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | 3 | # sgflib.py (Smart Game Format Parser Library) 4 | # Copyright (C) 2000 David John Goodger (dgoodger@bigfoot.com) 5 | # 6 | # This library is free software; you can redistribute it and/or modify it 7 | # under the terms of the GNU Lesser General Public License as published by the 8 | # Free Software Foundation; either version 2 of the License, or (at your 9 | # option) any later version. 10 | # 11 | # This library is distributed in the hope that it will be useful, but WITHOUT 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 14 | # for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # (lgpl.txt) along with this library; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | # The license is currently available on the Internet at: 20 | # http://www.gnu.org/copyleft/lesser.html 21 | 22 | """ 23 | ============================================= 24 | Smart Game Format Parser Library: sgflib.py 25 | ============================================= 26 | version 1.0 (2000-03-27) 27 | 28 | Homepage: [[http://gotools.sourceforge.net]] 29 | 30 | Copyright (C) 2000 David John Goodger ([[mailto:dgoodger@bigfoot.com]]; davidg 31 | on NNGS, IGS, goclub.org). sgflib.py comes with ABSOLUTELY NO WARRANTY. This is 32 | free software, and you are welcome to redistribute it and/or modify it under the 33 | terms of the GNU Lesser General Public License; see the source code for details. 34 | 35 | Description 36 | =========== 37 | This library contains a parser and classes for SGF, the Smart Game Format. SGF 38 | is a text only, tree based file format designed to store game records of board 39 | games for two players, most commonly for the game of go. (See the official SGF 40 | specification at [[http://www.POBoxes.com/sgf/]]). 41 | 42 | Given a string containing a complete SGF data instance, the 'SGFParser' class 43 | will create a 'Collection' object consisting of one or more 'GameTree''s (one 44 | 'GameTree' per game in the SGF file), each containing a sequence of 'Node''s and 45 | (potentially) two or more variation 'GameTree''s (branches). Each 'Node' 46 | contains an ordered dictionary of 'Property' ID/value pairs (note that values 47 | are lists, and can have multiple entries). 48 | 49 | Tree traversal methods are provided through the 'Cursor' class. 50 | 51 | The default representation (using 'str()' or 'print') of each class of SGF 52 | objects is the Smart Game Format itself.""" 53 | 54 | 55 | # Revision History: 56 | # 57 | # 1.0 (2000-03-27): First public release. 58 | # - Ready for prime time. 59 | # 60 | # 0.1 (2000-01-16): 61 | # - Initial idea & started coding. 62 | 63 | 64 | import string, re 65 | from typelib import List, Dictionary 66 | 67 | 68 | # Parsing Exceptions 69 | 70 | class EndOfDataParseError(Exception): 71 | """ Raised by 'SGFParser.parseVariations()', 'SGFParser.parseNode()'.""" 72 | pass 73 | 74 | class GameTreeParseError(Exception): 75 | """ Raised by 'SGFParser.parseGameTree()'.""" 76 | pass 77 | 78 | class NodePropertyParseError(Exception): 79 | """ Raised by 'SGFParser.parseNode()'.""" 80 | pass 81 | 82 | class PropertyValueParseError(Exception): 83 | """ Raised by 'SGFParser.parsePropertyValue()'.""" 84 | pass 85 | 86 | # Tree Construction Exceptions 87 | 88 | class DirectAccessError(Exception): 89 | """ Raised by 'Node.__setitem__()', 'Node.update()'.""" 90 | pass 91 | 92 | class DuplicatePropertyError(Exception): 93 | """ Raised by 'Node.addProperty()'.""" 94 | pass 95 | 96 | # Tree Navigation Exceptions 97 | class GameTreeNavigationError(Exception): 98 | """ Raised by 'Cursor.next()'.""" 99 | pass 100 | 101 | class GameTreeEndError(Exception): 102 | """ Raised by 'Cursor.next()', 'Cursor.previous()'.""" 103 | pass 104 | 105 | 106 | # for type checking 107 | INT_TYPE = type(0) # constant 108 | 109 | # miscellaneous constants 110 | MAX_LINE_LEN = 76 # constant; for line breaks 111 | 112 | 113 | class SGFParser: 114 | """ 115 | Parser for SGF data. Creates a tree structure based on the SGF standard 116 | itself. 'SGFParser.parse()' will return a 'Collection' object for the entire 117 | data. 118 | 119 | Instance Attributes: 120 | - self.data : string -- The complete SGF data instance. 121 | - self.datalen : integer -- Length of 'self.data'. 122 | - self.index : integer -- Current parsing position in 'self.data'. 123 | 124 | Class Attributes: 125 | - re* : re.RegexObject -- Regular expression text matching patterns. 126 | - ctrltrans: string[256] -- Control character translation table for 127 | string.translate(), used to remove all control characters from Property 128 | values. May be overridden (preferably in instances).""" 129 | 130 | # text matching patterns 131 | reGameTreeStart = re.compile(r'\s*\(') 132 | reGameTreeEnd = re.compile(r'\s*\)') 133 | reGameTreeNext = re.compile(r'\s*(;|\(|\))') 134 | reNodeContents = re.compile(r'\s*([A-Za-z]+(?=\s*\[))') 135 | rePropertyStart = re.compile(r'\s*\[') 136 | rePropertyEnd = re.compile(r'\]') 137 | reEscape = re.compile(r'\\') 138 | reLineBreak = re.compile(r'\r\n?|\n\r?') # CR, LF, CR/LF, LF/CR 139 | 140 | 141 | # character translation tables 142 | # for control characters (except LF \012 & CR \015): convert to spaces 143 | ctrltrans = string.maketrans("\000\001\002\003\004\005\006\007" + 144 | "\010\011\013\014\016\017\020\021\022\023\024\025\026\027" + 145 | "\030\031\032\033\034\035\036\037", " "*30) 146 | 147 | def __init__(self, data): 148 | """ Initialize the instance attributes. See the class itself for info.""" 149 | self.data = data 150 | self.datalen = len(data) 151 | self.index = 0 152 | 153 | def parse(self): 154 | """ Parses the SGF data stored in 'self.data', and returns a 'Collection'.""" 155 | c = Collection() 156 | while self.index < self.datalen: 157 | g = self.parseOneGame() 158 | if g: 159 | c.append(g) 160 | else: 161 | break 162 | return c 163 | 164 | def parseOneGame(self): 165 | """ Parses one game from 'self.data'. Returns a 'GameTree' containing 166 | one game, or 'None' if the end of 'self.data' has been reached.""" 167 | if self.index < self.datalen: 168 | match = self.reGameTreeStart.match(self.data, self.index) 169 | if match: 170 | self.index = match.end() 171 | return self.parseGameTree() 172 | return None 173 | 174 | def parseGameTree(self): 175 | """ Called when "(" encountered, ends when a matching ")" encountered. 176 | Parses and returns one 'GameTree' from 'self.data'. Raises 177 | 'GameTreeParseError' if a problem is encountered.""" 178 | g = GameTree() 179 | while self.index < self.datalen: 180 | match = self.reGameTreeNext.match(self.data, self.index) 181 | if match: 182 | self.index = match.end() 183 | if match.group(1) == ";": # found start of node 184 | if g.variations: 185 | raise GameTreeParseError( 186 | "A node was encountered after a variation.") 187 | g.append(g.makeNode(self.parseNode())) 188 | elif match.group(1) == "(": # found start of variation 189 | g.variations = self.parseVariations() 190 | else: # found end of GameTree ")" 191 | return g 192 | else: # error 193 | raise GameTreeParseError 194 | return g 195 | 196 | def parseVariations(self): 197 | """ Called when "(" encountered inside a 'GameTree', ends when a 198 | non-matching ")" encountered. Returns a list of variation 199 | 'GameTree''s. Raises 'EndOfDataParseError' if the end of 'self.data' 200 | is reached before the end of the enclosing 'GameTree'.""" 201 | v = [] 202 | while self.index < self.datalen: 203 | # check for ")" at end of GameTree, but don't consume it 204 | match = self.reGameTreeEnd.match(self.data, self.index) 205 | if match: 206 | return v 207 | g = self.parseGameTree() 208 | if g: 209 | v.append(g) 210 | # check for next variation, and consume "(" 211 | match = self.reGameTreeStart.match(self.data, self.index) 212 | if match: 213 | self.index = match.end() 214 | raise EndOfDataParseError 215 | 216 | def parseNode(self): 217 | """ Called when ";" encountered (& is consumed). Parses and returns one 218 | 'Node', which can be empty. Raises 'NodePropertyParseError' if no 219 | property values are extracted. Raises 'EndOfDataParseError' if the 220 | end of 'self.data' is reached before the end of the node (i.e., the 221 | start of the next node, the start of a variation, or the end of the 222 | enclosing game tree).""" 223 | n = Node() 224 | while self.index < self.datalen: 225 | match = self.reNodeContents.match(self.data, self.index) 226 | if match: 227 | self.index = match.end() 228 | pvlist = self.parsePropertyValue() 229 | if pvlist: 230 | n.addProperty(n.makeProperty(match.group(1), pvlist)) 231 | else: 232 | raise NodePropertyParseError 233 | else: # reached end of Node 234 | return n 235 | raise EndOfDataParseError 236 | 237 | def parsePropertyValue(self): 238 | """ Called when "[" encountered (but not consumed), ends when the next 239 | property, node, or variation encountered. Parses and returns a list 240 | of property values. Raises 'PropertyValueParseError' if there is a 241 | problem.""" 242 | pvlist = [] 243 | while self.index < self.datalen: 244 | match = self.rePropertyStart.match(self.data, self.index) 245 | if match: 246 | self.index = match.end() 247 | v = "" # value 248 | # scan for escaped characters (using '\'), unescape them (remove linebreaks) 249 | mend = self.rePropertyEnd.search(self.data, self.index) 250 | mesc = self.reEscape.search(self.data, self.index) 251 | while mesc and mend and (mesc.end() < mend.end()): 252 | # copy up to '\', but remove '\' 253 | v = v + self.data[self.index:mesc.start()] 254 | mbreak = self.reLineBreak.match(self.data, mesc.end()) 255 | if mbreak: 256 | self.index = mbreak.end() # remove linebreak 257 | else: 258 | v = v + self.data[mesc.end()] # copy escaped character 259 | self.index = mesc.end() + 1 # move to point after escaped char 260 | mend = self.rePropertyEnd.search(self.data, self.index) 261 | mesc = self.reEscape.search(self.data, self.index) 262 | if mend: 263 | v = v + self.data[self.index:mend.start()] 264 | self.index = mend.end() 265 | pvlist.append(self._convertControlChars(v)) 266 | else: 267 | raise PropertyValueParseError 268 | else: # reached end of Property 269 | break 270 | if len(pvlist) >= 1: 271 | return pvlist 272 | else: 273 | raise PropertyValueParseError 274 | 275 | def _convertControlChars(self, text): 276 | """ Converts control characters in 'text' to spaces, using the 277 | 'self.ctrltrans' translation table. Override for variant 278 | behaviour.""" 279 | return string.translate(text, self.ctrltrans) 280 | 281 | 282 | class RootNodeSGFParser(SGFParser): 283 | """ For parsing only the first 'GameTree''s root Node of an SGF file.""" 284 | 285 | def parseNode(self): 286 | """ Calls 'SGFParser.parseNode()', sets 'self.index' to point to the end 287 | of the data (effectively ending the 'GameTree' and 'Collection'), 288 | and returns the single (root) 'Node' parsed.""" 289 | n = SGFParser.parseNode(self) # process one Node as usual 290 | self.index = self.datalen # set end of data 291 | return n # we're only interested in the root node 292 | 293 | 294 | class Collection(List): 295 | """ 296 | An SGF collection: multiple 'GameTree''s. Instance atributes: 297 | - self[.data] : list of 'GameTree' -- One 'GameTree' per game.""" 298 | 299 | def __str__(self): 300 | """ SGF representation. Separates game trees with a blank line.""" 301 | return string.join(map(str, self.data), "\n"*2) 302 | 303 | def cursor(self, gamenum=0): 304 | """ Returns a 'Cursor' object for navigation of the given 'GameTree'.""" 305 | return Cursor(self[gamenum]) 306 | 307 | 308 | class GameTree(List): 309 | """ 310 | An SGF game tree: a game or variation. Instance attributes: 311 | - self[.data] : list of 'Node' -- game tree 'trunk'. 312 | - self.variations : list of 'GameTree' -- 0 or 2+ variations. 313 | 'self.variations[0]' contains the main branch (sequence actually played).""" 314 | 315 | def __init__(self, nodelist=None, variations=None): 316 | """ 317 | Initialize the 'GameTree'. Arguments: 318 | - nodelist : 'GameTree' or list of 'Node' -- Stored in 'self.data'. 319 | - variations : list of 'GameTree' -- Stored in 'self.variations'.""" 320 | List.__init__(self, nodelist) 321 | self.variations = variations or [] 322 | 323 | def __str__(self): 324 | """ SGF representation, with proper line breaks between nodes.""" 325 | if len(self): 326 | s = "(" + str(self[0]) # append the first Node automatically 327 | l = len(string.split(s, "\n")[-1]) # accounts for line breaks within Nodes 328 | for n in map(str, self[1:]): 329 | if l + len(string.split(n, "\n")[0]) > MAX_LINE_LEN: 330 | s = s + "\n" 331 | l = 0 332 | s = s + n 333 | l = len(string.split(s, "\n")[-1]) 334 | return s + string.join(map(str, [""] + self.variations), "\n") + ")" 335 | else: 336 | return "" # empty GameTree illegal; "()" illegal 337 | 338 | def mainline(self): 339 | """ Returns the main line of the game (variation A) as a 'GameTree'.""" 340 | if self.variations: 341 | return GameTree(self.data + self.variations[0].mainline()) 342 | else: 343 | return self 344 | 345 | def makeNode(self, plist): 346 | """ 347 | Create a new 'Node' containing the properties contained in 'plist'. 348 | Override/extend to create 'Node' subclass instances (move, setup). 349 | Argument: 350 | - plist : 'Node' or list of 'Property'""" 351 | return Node(plist) 352 | 353 | def cursor(self): 354 | """ Returns a 'Cursor' object for navigation of this 'GameTree'.""" 355 | return Cursor(self) 356 | 357 | def propertySearch(self, pid, getall=0): 358 | """ 359 | Searches this 'GameTree' for nodes containing matching properties. 360 | Returns a 'GameTree' containing the matched node(s). Arguments: 361 | - pid : string -- ID of properties to search for. 362 | - getall : boolean -- Set to true (1) to return all 'Node''s that 363 | match, or to false (0) to return only the first match.""" 364 | matches = [] 365 | for n in self: 366 | if n.has_key(pid): 367 | matches.append(n) 368 | if not getall: 369 | break 370 | else: # getall or not matches: 371 | for v in self.variations: 372 | matches = matches + v.propertySearch(pid, getall) 373 | if not getall and matches: 374 | break 375 | return GameTree(matches) 376 | 377 | 378 | class Node(Dictionary): 379 | """ 380 | An SGF node. Instance Attributes: 381 | - self[.data] : ordered dictionary -- '{Property.id:Property}' mapping. 382 | (Ordered dictionary: allows offset-indexed retrieval). Properties *must* 383 | be added using 'self.addProperty()'. 384 | 385 | Example: Let 'n' be a 'Node' parsed from ';B[aa]BL[250]C[comment]': 386 | - 'str(n["BL"])' => '"BL[250]"' 387 | - 'str(n[0])' => '"B[aa]"' 388 | - 'map(str, n)' => '["B[aa]","BL[250]","C[comment]"]'""" 389 | 390 | def __init__(self, plist=[]): 391 | """ 392 | Initializer. Argument: 393 | - plist: Node or list of 'Property'.""" 394 | Dictionary.__init__(self) 395 | self.order = [] 396 | for p in plist: 397 | self.addProperty(p) 398 | 399 | def __getitem__(self, key): 400 | """ On 'self[key]', 'x in self', 'for x in self'. Implements all 401 | indexing-related operations. Allows both key- and offset-indexed 402 | retrieval. Membership and iteration ('in', 'for') repeatedly index 403 | from 0 until 'IndexError'.""" 404 | if type(key) is INT_TYPE: 405 | return self.order[key] 406 | else: 407 | return self.data[key] 408 | 409 | def __setitem__(self, key, x): 410 | """ On 'self[key]=x'. Allows assignment to existing items only. Raises 411 | 'DirectAccessError' on new item assignment.""" 412 | if self.has_key(key): 413 | self.order[self.order.index(self[key])] = x 414 | Dictionary.__setitem__(self, key, x) 415 | else: 416 | raise DirectAccessError( 417 | "Properties may not be added directly; use addProperty() instead.") 418 | 419 | def __delitem__(self, key): 420 | """ On 'del self[key]'. Updates 'self.order' to maintain consistency.""" 421 | self.order.remove(self[key]) 422 | Dictionary.__delitem__(self, key) 423 | 424 | def __getslice__(self, low, high): 425 | """ On 'self[low:high]'.""" 426 | return self.order[low:high] 427 | 428 | def __str__(self): 429 | """ SGF representation, with proper line breaks between properties.""" 430 | if len(self): 431 | s = ";" + str(self[0]) 432 | l = len(string.split(s, "\n")[-1]) # accounts for line breaks within Properties 433 | for p in map(str, self[1:]): 434 | if l + len(string.split(p, "\n")[0]) > MAX_LINE_LEN: 435 | s = s + "\n" 436 | l = 0 437 | s = s + p 438 | l = len(string.split(s, "\n")[-1]) 439 | return s 440 | else: 441 | return ";" 442 | 443 | def update(self, dict): 444 | """ 'Dictionary' method not applicable to 'Node'. Raises 445 | 'DirectAccessError'.""" 446 | raise DirectAccessError( 447 | "The update() method is not supported by Node; use addProperty() instead.") 448 | 449 | def addProperty(self, property): 450 | """ 451 | Adds a 'Property' to this 'Node'. Checks for duplicate properties 452 | (illegal), and maintains the property order. Argument: 453 | - property : 'Property'""" 454 | if self.has_key(property.id): 455 | raise DuplicatePropertyError 456 | else: 457 | self.data[property.id] = property 458 | self.order.append(property) 459 | 460 | def makeProperty(self, id, valuelist): 461 | """ 462 | Create a new 'Property'. Override/extend to create 'Property' 463 | subclass instances (move, setup, game-info, etc.). Arguments: 464 | - id : string 465 | - valuelist : 'Property' or list of values""" 466 | return Property(id, valuelist) 467 | 468 | 469 | class Property(List): 470 | """ 471 | An SGF property: a set of label and value(s). Instance attributes: 472 | - self[.data] : list -- property values. 473 | - self.id : string -- SGF standard property label. 474 | - self.name : string -- actual label used in the SGF data. For example, the 475 | property 'CoPyright[...]' has name 'CoPyright' and id 'CP'.""" 476 | 477 | def __init__(self, id, values, name=None): 478 | """ 479 | Initialize the 'Property'. Arguments: 480 | - id : string 481 | - name : string (optional) -- If not given, 'self.name' 482 | - nodelist : 'GameTree' or list of 'Node' -- Stored in 'self.data'. 483 | - variations : list of 'GameTree' -- Stored in 'self.variations'.""" 484 | List.__init__(self, values) # XXX will _convert work here? 485 | self.id = id 486 | self.name = name or id 487 | 488 | def __str__(self): 489 | return self.name + "[" + string.join(map(_escapeText, self), "][") + "]" 490 | 491 | 492 | class Cursor: 493 | """ 494 | 'GameTree' navigation tool. Instance attributes: 495 | - self.game : 'GameTree' -- The root 'GameTree'. 496 | - self.gametree : 'GameTree' -- The current 'GameTree'. 497 | - self.node : 'Node' -- The current Node. 498 | - self.nodenum : integer -- The offset of 'self.node' from the root of 499 | 'self.game'. The nodenum of the root node is 0. 500 | - self.index : integer -- The offset of 'self.node' within 'self.gametree'. 501 | - self.stack : list of 'GameTree' -- A record of 'GameTree''s traversed. 502 | - self.children : list of 'Node' -- All child nodes of the current node. 503 | - self.atEnd : boolean -- Flags if we are at the end of a branch. 504 | - self.atStart : boolean -- Flags if we are at the start of the game.""" 505 | 506 | def __init__(self, gametree): 507 | """ Initialize root 'GameTree' and instance variables.""" 508 | self.game = gametree # root GameTree 509 | self.reset() 510 | 511 | def reset(self): 512 | """ Set 'Cursor' to point to the start of the root 'GameTree', 'self.game'.""" 513 | self.gametree = self.game 514 | self.nodenum = 0 515 | self.index = 0 516 | self.stack = [] 517 | self.node = self.gametree[self.index] 518 | self._setChildren() 519 | self._setFlags() 520 | 521 | def next(self, varnum=0): 522 | """ 523 | Moves the 'Cursor' to & returns the next 'Node'. Raises 524 | 'GameTreeEndError' if the end of a branch is exceeded. Raises 525 | 'GameTreeNavigationError' if a non-existent variation is accessed. 526 | Argument: 527 | - varnum : integer, default 0 -- Variation number. Non-zero only 528 | valid at a branching, where variations exist.""" 529 | if self.index + 1 < len(self.gametree): # more main line? 530 | if varnum != 0: 531 | raise GameTreeNavigationError("Nonexistent variation.") 532 | self.index = self.index + 1 533 | elif self.gametree.variations: # variations exist? 534 | if varnum < len(self.gametree.variations): 535 | self.stack.append(self.gametree) 536 | self.gametree = self.gametree.variations[varnum] 537 | self.index = 0 538 | else: 539 | raise GameTreeNavigationError("Nonexistent variation.") 540 | else: 541 | raise GameTreeEndError 542 | self.node = self.gametree[self.index] 543 | self.nodenum = self.nodenum + 1 544 | self._setChildren() 545 | self._setFlags() 546 | return self.node 547 | 548 | def previous(self): 549 | """ Moves the 'Cursor' to & returns the previous 'Node'. Raises 550 | 'GameTreeEndError' if the start of a branch is exceeded.""" 551 | if self.index - 1 >= 0: # more main line? 552 | self.index = self.index - 1 553 | elif self.stack: # were we in a variation? 554 | self.gametree = self.stack.pop() 555 | self.index = len(self.gametree) - 1 556 | else: 557 | raise GameTreeEndError 558 | self.node = self.gametree[self.index] 559 | self.nodenum = self.nodenum - 1 560 | self._setChildren() 561 | self._setFlags() 562 | return self.node 563 | 564 | def _setChildren(self): 565 | """ Sets up 'self.children'.""" 566 | if self.index + 1 < len(self.gametree): 567 | self.children = [self.gametree[self.index+1]] 568 | else: 569 | self.children = map(lambda list: list[0], self.gametree.variations) 570 | 571 | def _setFlags(self): 572 | """ Sets up the flags 'self.atEnd' and 'self.atStart'.""" 573 | self.atEnd = not self.gametree.variations and (self.index + 1 == len(self.gametree)) 574 | self.atStart = not self.stack and (self.index == 0) 575 | 576 | 577 | reCharsToEscape = re.compile(r'\]|\\') # characters that need to be \escaped 578 | 579 | def _escapeText(text): 580 | """ Adds backslash-escapes to property value characters that need them.""" 581 | output = "" 582 | index = 0 583 | match = reCharsToEscape.search(text, index) 584 | while match: 585 | output = output + text[index:match.start()] + '\\' + text[match.start()] 586 | index = match.end() 587 | match = reCharsToEscape.search(text, index) 588 | output = output + text[index:] 589 | return output 590 | 591 | 592 | def selfTest1(onConsole=0): 593 | """ Canned data test case""" 594 | sgfdata = r""" (;GM [1]US[someone]CoPyright[\ 595 | Permission to reproduce this game is given.]GN[a-b]EV[None]RE[B+Resign] 596 | PW[a]WR[2k*]PB[b]BR[4k*]PC[somewhere]DT[2000-01-16]SZ[19]TM[300]KM[4.5] 597 | HA[3]AB[pd][dp][dd];W[pp];B[nq];W[oq]C[ x started observation. 598 | ](;B[qc]C[ [b\]: \\ hi x! ;-) \\];W[kc])(;B[hc];W[oe])) """ 599 | print "\n\n********** Self-Test 1 **********\n" 600 | print "Input data:\n" 601 | print sgfdata 602 | print "\n\nParsed data: " 603 | col = SGFParser(sgfdata).parse() 604 | print "done\n" 605 | cstr = str(col) 606 | print cstr, "\n" 607 | print "Mainline:\n" 608 | m = col[0].mainline() 609 | print m, "\n" 610 | ##print "as GameTree:\n" 611 | ##print GameTree(m), "\n" 612 | print "Tree traversal (forward):\n" 613 | c = col.cursor() 614 | while 1: 615 | print "nodenum: %s; index: %s; children: %s; node: %s" % (c.nodenum, c.index, len(c.children), c.node) 616 | if c.atEnd: break 617 | c.next() 618 | print "\nTree traversal (backward):\n" 619 | while 1: 620 | print "nodenum: %s; index: %s; children: %s; node: %s" % (c.nodenum, c.index, len(c.children), c.node) 621 | if c.atStart: break 622 | c.previous() 623 | print "\nSearch for property 'B':" 624 | print col[0].propertySearch("B", 1) 625 | print "\nSearch for property 'C':" 626 | print col[0].propertySearch("C", 1) 627 | pass 628 | 629 | def selfTest2(onConsole=0): 630 | """ Macintosh-based SGF file test""" 631 | import macfs 632 | print "\n\n********** Self-Test 2 (Mac) **********\n" 633 | thefile = macfs.PromptGetFile("Please choose an SGF file:") 634 | if not thefile[1]: 635 | return 636 | srcpath = thefile[0].as_pathname() 637 | src = open(srcpath, 'r') 638 | sgfdata = src.read() 639 | print "Input data:\n" 640 | print sgfdata 641 | print "\n\nParsed data:" 642 | col = SGFParser(sgfdata).parse() 643 | print "done\n" 644 | print str(col) 645 | 646 | 647 | if __name__ == '__main__': 648 | print __doc__ # show module's documentation string 649 | selfTest1() 650 | import os 651 | if os.name == 'mac': 652 | selfTest2() 653 | -------------------------------------------------------------------------------- /data/utils/sgflib/typelib.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python 2 | 3 | # typelib.py (Type Class Library) 4 | # Copyright (c) 2000 David John Goodger 5 | # 6 | # This software is provided "as-is", without any express or implied warranty. 7 | # In no event will the authors be held liable for any damages arising from the 8 | # use of this software. 9 | # 10 | # Permission is granted to anyone to use this software for any purpose, 11 | # including commercial applications, and to alter it and redistribute it 12 | # freely, subject to the following restrictions: 13 | # 14 | # 1. The origin of this software must not be misrepresented; you must not 15 | # claim that you wrote the original software. If you use this software in a 16 | # product, an acknowledgment in the product documentation would be appreciated 17 | # but is not required. 18 | # 19 | # 2. Altered source versions must be plainly marked as such, and must not be 20 | # misrepresented as being the original software. 21 | # 22 | # 3. This notice may not be removed or altered from any source distribution. 23 | 24 | """ 25 | ================================ 26 | Type Class Library: typelib.py 27 | ================================ 28 | version 1.0 (2000-03-27) 29 | 30 | Homepage: [[http://gotools.sourceforge.net/]] (see sgflib.py) 31 | 32 | Copyright (C) 2000 David John Goodger ([[mailto:dgoodger@bigfoot.com]]). 33 | typelib.py comes with ABSOLUTELY NO WARRANTY. This is free software, and you are 34 | welcome to redistribute it and/or modify it under certain conditions; see the 35 | source code for details. 36 | 37 | Description 38 | =========== 39 | This library implements abstract superclasses to emulate Python's built-in data 40 | types. This is useful when you want a class which acts like a built-in type, but 41 | with added/modified behaviour (methods) and/or data (attributes). 42 | 43 | Implemented types are: 'String', 'Tuple', 'List', 'Dictionary', 'Integer', 44 | 'Long', 'Float', 'Complex' (along with their abstract superclasses). 45 | 46 | All methods, including special overloading methods, are implemented for each 47 | type-emulation class. Instance data is stored internally in the 'data' attribute 48 | (i.e., 'self.data'). The type the class is emulating is stored in the class 49 | attribute 'self.TYPE' (as given by the built-in 'type(class)'). The 50 | 'SuperClass.__init__()' method uses two class-specific methods to instantiate 51 | objects: '_reset()' and '_convert()'. 52 | 53 | See "sgflib.py" (at module's homepage, see above) for examples of use. The Node 54 | class is of particular interest: a modified 'Dictionary' which is ordered and 55 | allows for offset-indexed retrieval.""" 56 | 57 | 58 | # Revision History 59 | # 60 | # 1.0 (2000-03-27): First public release. 61 | # - Implemented Integer, Long, Float, and Complex. 62 | # - Cleaned up a few loose ends. 63 | # - Completed docstring documentatation. 64 | # 65 | # 0.1 (2000-01-27): 66 | # - Implemented String, Tuple, List, and Dictionary emulation. 67 | # 68 | # To do: 69 | # - Implement Function? File? (Have to come up with a good reason first ;-) 70 | 71 | 72 | class SuperType: 73 | """ Superclass of all type classes. Implements methods common to all types. 74 | Concrete (as opposed to abstract) subclasses must define a class 75 | attribute 'self.TYPE' ('=type(Class)'), and methods '_reset(self)' and 76 | '_convert(self, data)'.""" 77 | 78 | def __init__(self, data=None): 79 | """ 80 | On 'Class()', initialize 'self.data'. Argument: 81 | - 'data' : optional, default 'None' -- 82 | - If the type of 'data' is identical to the Class' 'TYPE', 83 | 'data' will be shared (relevant for mutable types only). 84 | - If 'data' is given (and not false), it will be converted by 85 | the Class-specific method 'self._convert(data)'. Incompatible 86 | data types will raise an exception. 87 | - If 'data' is 'None', false, or not given, a Class-specific method 88 | 'self._reset()' is called to initialize an empty instance.""" 89 | if data: 90 | if type(data) is self.TYPE: 91 | self.data = data 92 | else: 93 | self.data = self._convert(data) 94 | else: 95 | self._reset() 96 | 97 | def __str__(self): 98 | """ On 'str(self)' and 'print self'. Returns string representation.""" 99 | return str(self.data) 100 | 101 | def __cmp__(self, x): 102 | """ On 'self>x', 'self==x', 'cmp(self,x)', etc. Catches all 103 | comparisons: returns -1, 0, or 1 for less, equal, or greater.""" 104 | return cmp(self.data, x) 105 | 106 | def __rcmp__(self, x): 107 | """ On 'x>self', 'x==self', 'cmp(x,self)', etc. Catches all 108 | comparisons: returns -1, 0, or 1 for less, equal, or greater.""" 109 | return cmp(x, self.data) 110 | 111 | def __hash__(self): 112 | """ On 'dictionary[self]', 'hash(self)'. Returns a unique and unchanging 113 | integer hash-key.""" 114 | return hash(self.data) 115 | 116 | 117 | class AddMulMixin: 118 | """ Addition & multiplication for numbers, concatenation & repetition for 119 | sequences.""" 120 | 121 | def __add__(self, other): 122 | """ On 'self+other'. Numeric addition, or sequence concatenation.""" 123 | return self.data + other 124 | 125 | def __radd__(self, other): 126 | """ On 'other+self'. Numeric addition, or sequence concatenation.""" 127 | return other + self.data 128 | 129 | def __mul__(self, other): 130 | """ On 'self*other'. Numeric multiplication, or sequence repetition.""" 131 | return self.data * other 132 | 133 | def __rmul__(self, other): 134 | """ On 'other*self'. Numeric multiplication, or sequence repetition.""" 135 | return other * self.data 136 | 137 | 138 | class MutableMixin: 139 | """ Assignment to and deletion of collection component.""" 140 | 141 | def __setitem__(self, key, x): 142 | """ On 'self[key]=x'.""" 143 | self.data[key] = x 144 | 145 | def __delitem__(self, key): 146 | """ On 'del self[key]'.""" 147 | del self.data[key] 148 | 149 | 150 | class ModMixin: 151 | """ Modulo remainder and string formatting.""" 152 | 153 | def __mod__(self, other): 154 | """ On 'self%other'.""" 155 | return self.data % other 156 | 157 | def __rmod__(self, other): 158 | """ On 'other%self'.""" 159 | return other % self.data 160 | 161 | 162 | class Number(SuperType, AddMulMixin, ModMixin): 163 | """ Superclass for numeric emulation types.""" 164 | 165 | def __sub__(self, other): 166 | """ On 'self-other'.""" 167 | return self.data - other 168 | 169 | def __rsub__(self, other): 170 | """ On 'other-self'.""" 171 | return other - self.data 172 | 173 | def __div__(self, other): 174 | """ On 'self/other'.""" 175 | return self.data / other 176 | 177 | def __rdiv__(self, other): 178 | """ On 'other/self'.""" 179 | return other / self.data 180 | 181 | def __divmod__(self, other): 182 | """ On 'divmod(self,other)'.""" 183 | return divmod(self.data, other) 184 | 185 | def __rdivmod__(self, other): 186 | """ On 'divmod(other,self)'.""" 187 | return divmod(other, self.data) 188 | 189 | def __pow__(self, other, mod=None): 190 | """ On 'pow(self,other[,mod])', 'self**other'.""" 191 | if mod is None: 192 | return self.data ** other 193 | else: 194 | return pow(self.data, other, mod) 195 | 196 | def __rpow__(self, other): 197 | """ On 'pow(other,self)', 'other**self'.""" 198 | return other ** self.data 199 | 200 | def __neg__(self): 201 | """ On '-self'.""" 202 | return -self.data 203 | 204 | def __pos__(self): 205 | """ On '+self'.""" 206 | return +self.data 207 | 208 | def __abs__(self): 209 | """ On 'abs(self)'.""" 210 | return abs(self.data) 211 | 212 | def __int__(self): 213 | """ On 'int(self)'.""" 214 | return int(self.data) 215 | 216 | def __long__(self): 217 | """ On 'long(self)'.""" 218 | return long(self.data) 219 | 220 | def __float__(self): 221 | """ On 'float(self)'.""" 222 | return float(self.data) 223 | 224 | def __complex__(self): 225 | """ On 'complex(self)'.""" 226 | return complex(self.data) 227 | 228 | def __nonzero__(self): 229 | """ On truth-value (or uses '__len__()' if defined).""" 230 | return self.data != 0 231 | 232 | def __coerce__(self, other): 233 | """ On mixed-type expression, 'coerce()'. Returns tuple of '(self, other)' 234 | converted to a common type.""" 235 | return coerce(self.data, other) 236 | 237 | 238 | class Integer(Number): 239 | """ Emulates a Python integer.""" 240 | 241 | TYPE = type(1) 242 | 243 | def _reset(self): 244 | """ Initialize an integer.""" 245 | self.data = 0 246 | 247 | def _convert(self, data): 248 | """ Convert data into an integer.""" 249 | return int(data) 250 | 251 | def __lshift__(self, other): 252 | """ On 'self<>other'.""" 261 | return self.data >> other 262 | 263 | def __rrshift__(self, other): 264 | """ On 'other>>self'.""" 265 | return other >> self.data 266 | 267 | def __and__(self, other): 268 | """ On 'self&other'.""" 269 | return self.data & other 270 | 271 | def __rand__(self, other): 272 | """ On 'other&self'.""" 273 | return other & self.data 274 | 275 | def __or__(self, other): 276 | """ On 'self|other'.""" 277 | return self.data | other 278 | 279 | def __ror__(self, other): 280 | """ On 'other|self'.""" 281 | return other | self.data 282 | 283 | def __xor__(self, other): 284 | """ On 'self^other'.""" 285 | return self.data ^ other 286 | 287 | def __rxor__(self, other): 288 | """ On 'other%self'.""" 289 | return other % self.data 290 | 291 | def __invert__(self): 292 | """ On '~self'.""" 293 | return ~self.data 294 | 295 | def __oct__(self): 296 | """ On 'oct(self)'. Returns octal string representation.""" 297 | return oct(self.data) 298 | 299 | def __hex__(self): 300 | """ On 'hex(self)'. Returns hexidecimal string representation.""" 301 | return hex(self.data) 302 | 303 | 304 | class Long(Integer): 305 | """ Emulates a Python long integer.""" 306 | 307 | TYPE = type(1L) 308 | 309 | def _reset(self): 310 | """ Initialize an integer.""" 311 | self.data = 0L 312 | 313 | def _convert(self, data): 314 | """ Convert data into an integer.""" 315 | return long(data) 316 | 317 | 318 | class Float(Number): 319 | """ Emulates a Python floating-point number.""" 320 | 321 | TYPE = type(0.1) 322 | 323 | def _reset(self): 324 | """ Initialize a float.""" 325 | self.data = 0.0 326 | 327 | def _convert(self, data): 328 | """ Convert data into a float.""" 329 | return float(data) 330 | 331 | 332 | class Complex(Number): 333 | """ Emulates a Python complex number.""" 334 | 335 | TYPE = type(0+0j) 336 | 337 | def _reset(self): 338 | """ Initialize an integer.""" 339 | self.data = 0+0j 340 | 341 | def _convert(self, data): 342 | """ Convert data into an integer.""" 343 | return complex(data) 344 | 345 | def __getattr__(self, name): 346 | """ On 'self.real' & 'self.imag'.""" 347 | if name == "real": 348 | return self.data.real 349 | elif name == "imag": 350 | return self.data.imag 351 | else: 352 | raise AttributeError(name) 353 | 354 | def conjugate(self): 355 | """ On 'self.conjugate()'.""" 356 | return self.data.conjugate() 357 | 358 | 359 | class Container(SuperType): 360 | """ Superclass for countable, indexable collection types ('Sequence', 'Mapping').""" 361 | 362 | def __len__(self): 363 | """ On 'len(self)', truth-value tests. Returns sequence or mapping 364 | collection size. Zero means false.""" 365 | return len(self.data) 366 | 367 | def __getitem__(self, key): 368 | """ On 'self[key]', 'x in self', 'for x in self'. Implements all 369 | indexing-related operations. Membership and iteration ('in', 'for') 370 | repeatedly index from 0 until 'IndexError'.""" 371 | return self.data[key] 372 | 373 | 374 | class Sequence(Container, AddMulMixin): 375 | """ Superclass for classes which emulate sequences ('List', 'Tuple', 'String').""" 376 | 377 | def __getslice__(self, low, high): 378 | """ On 'self[low:high]'.""" 379 | return self.data[low:high] 380 | 381 | 382 | class String(Sequence, ModMixin): 383 | """ Emulates a Python string.""" 384 | 385 | TYPE = type("") 386 | 387 | def _reset(self): 388 | """ Initialize an empty string.""" 389 | self.data = "" 390 | 391 | def _convert(self, data): 392 | """ Convert data into a string.""" 393 | return str(data) 394 | 395 | 396 | class Tuple(Sequence): 397 | """ Emulates a Python tuple.""" 398 | 399 | TYPE = type(()) 400 | 401 | def _reset(self): 402 | """ Initialize an empty tuple.""" 403 | self.data = () 404 | 405 | def _convert(self, data): 406 | """ Non-tuples cannot be converted. Raise an exception.""" 407 | raise TypeError("Non-tuples cannot be converted to a tuple.") 408 | 409 | 410 | class MutableSequence(Sequence, MutableMixin): 411 | """ Superclass for classes which emulate mutable (modifyable in-place) 412 | sequences ('List').""" 413 | 414 | def __setslice__(self, low, high, seq): 415 | """ On 'self[low:high]=seq'.""" 416 | self.data[low:high] = seq 417 | 418 | def __delslice__(self, low, high): 419 | """ On 'del self[low:high]'.""" 420 | del self.data[low:high] 421 | 422 | def append(self, x): 423 | """ Inserts object 'x' at the end of 'self.data' in-place.""" 424 | self.data.append(x) 425 | 426 | def count(self, x): 427 | """ Returns the number of occurrences of 'x' in 'self.data'.""" 428 | return self.data.count(x) 429 | 430 | def extend(self, x): 431 | """ Concatenates sequence 'x' to the end of 'self' in-place 432 | (like 'self=self+x').""" 433 | self.data.extend(x) 434 | 435 | def index(self, x): 436 | """ Returns the offset of the first occurrence of object 'x' in 437 | 'self.data'; raises an exception if not found.""" 438 | return self.data.index(x) 439 | 440 | def insert(self, i, x): 441 | """ Inserts object 'x' into 'self.data' at offset 'i' 442 | (like 'self[i:i]=[x]').""" 443 | self.data.insert(i, x) 444 | 445 | def pop(self, i=-1): 446 | """ Returns and deletes the last item of 'self.data' (or item 447 | 'self.data[i]' if 'i' given).""" 448 | return self.data.pop(i) 449 | 450 | def remove(self, x): 451 | """ Deletes the first occurrence of object 'x' from 'self.data'; 452 | raise an exception if not found.""" 453 | self.data.remove(x) 454 | 455 | def reverse(self): 456 | """ Reverses items in 'self.data' in-place.""" 457 | self.data.reverse() 458 | 459 | def sort(self, func=None): 460 | """ 461 | Sorts 'self.data' in-place. Argument: 462 | - func : optional, default 'None' -- 463 | - If 'func' not given, sorting will be in ascending 464 | order. 465 | - If 'func' given, it will determine the sort order. 466 | 'func' must be a two-argument comparison function 467 | which returns -1, 0, or 1, to mean before, same, 468 | or after ordering.""" 469 | if func: 470 | self.data.sort(func) 471 | else: 472 | self.data.sort() 473 | 474 | 475 | class List(MutableSequence): 476 | """ Emulates a Python list. When instantiating an object with data 477 | ('List(data)'), you can force a copy with 'List(list(data))'.""" 478 | 479 | TYPE = type([]) 480 | 481 | def _reset(self): 482 | """ Initialize an empty list.""" 483 | self.data = [] 484 | 485 | def _convert(self, data): 486 | """ Convert data into a list.""" 487 | return list(data) 488 | 489 | 490 | class Mapping(Container): 491 | """ Superclass for classes which emulate mappings/hashes ('Dictionary').""" 492 | 493 | def has_key(self, key): 494 | """ Returns 1 (true) if 'self.data' has a key 'key', or 0 otherwise.""" 495 | return self.data.has_key(key) 496 | 497 | def keys(self): 498 | """ Returns a new list holding all keys from 'self.data'.""" 499 | return self.data.keys() 500 | 501 | def values(self): 502 | """ Returns a new list holding all values from 'self.data'.""" 503 | return self.data.values() 504 | 505 | def items(self): 506 | """ Returns a new list of tuple pairs '(key, value)', one for each entry 507 | in 'self.data'.""" 508 | return self.data.items() 509 | 510 | def clear(self): 511 | """ Removes all items from 'self.data'.""" 512 | self.data.clear() 513 | 514 | def get(self, key, default=None): 515 | """ Similar to 'self[key]', but returns 'default' (or 'None') instead of 516 | raising an exception when 'key' is not found in 'self.data'.""" 517 | return self.data.get(key, default) 518 | 519 | def copy(self): 520 | """ Returns a shallow (top-level) copy of 'self.data'.""" 521 | return self.data.copy() 522 | 523 | def update(self, dict): 524 | """ Merges 'dict' into 'self.data' 525 | (i.e., 'for (k,v) in dict.items(): self.data[k]=v').""" 526 | self.data.update(dict) 527 | 528 | 529 | class Dictionary(Mapping, MutableMixin): 530 | """ Emulates a Python dictionary, a mutable mapping. When instantiating an 531 | object with data ('Dictionary(data)'), you can force a (shallow) copy 532 | with 'Dictionary(data.copy())'.""" 533 | 534 | TYPE = type({}) 535 | 536 | def _reset(self): 537 | """ Initialize an empty dictionary.""" 538 | self.data = {} 539 | 540 | def _convert(self, data): 541 | """ Non-dictionaries cannot be converted. Raise an exception.""" 542 | raise TypeError("Non-dictionaries cannot be converted to a dictionary.") 543 | 544 | 545 | if __name__ == "__main__": 546 | print __doc__ # show module's documentation string 547 | -------------------------------------------------------------------------------- /interface/opponents/pachi/pachi.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pycharming/AlphaGo/8240092c7282dff4620b532d9b01440f6485c849/interface/opponents/pachi/pachi.py -------------------------------------------------------------------------------- /interface/server/go.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Go 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 |
13 |
14 | Sorry, your browser doesn't support WGo.js. Download SGF directly. 15 |
16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /interface/server/goServer.py: -------------------------------------------------------------------------------- 1 | __all__ = ["SimpleHTTPRequestHandler"] 2 | 3 | import os 4 | import posixpath 5 | import BaseHTTPServer 6 | import urllib 7 | import cgi 8 | import shutil 9 | import mimetypes 10 | import re 11 | 12 | try: 13 | from cStringIO import StringIO 14 | except ImportError: 15 | from StringIO import StringIO 16 | 17 | class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 18 | 19 | """Simple HTTP request handler with GET/HEAD/POST commands. 20 | 21 | This serves files from the current directory and any of its 22 | subdirectories. The MIME type for files is determined by 23 | calling the .guess_type() method. And can reveive file uploaded 24 | by client. 25 | 26 | The GET/HEAD/POST requests are identical except that the HEAD 27 | request omits the actual contents of the file. 28 | 29 | """ 30 | 31 | server_version = "SimpleHTTPWithUpload/" 32 | 33 | def do_GET(self): 34 | f = self.send_head() 35 | if f: 36 | self.copyfile(f, self.wfile) 37 | f.close() 38 | 39 | def do_HEAD(self): 40 | f = self.send_head() 41 | if f: 42 | f.close() 43 | 44 | def do_POST(self): 45 | r, info = self.deal_post_data() 46 | print r, info, "by: ", self.client_address 47 | 48 | fileName = os.path.basename(info) 49 | fileName = fileName.split('.')[0] 50 | 51 | self.send_response(301) 52 | self.send_header('Location', 'http://localhost:8000/go.html?file=' + fileName) 53 | self.end_headers() 54 | 55 | def deal_post_data(self): 56 | boundary = self.headers.plisttext.split("=")[1] 57 | remainbytes = int(self.headers['content-length']) 58 | line = self.rfile.readline() 59 | remainbytes -= len(line) 60 | if not boundary in line: 61 | return (False, "Content NOT begin with boundary") 62 | line = self.rfile.readline() 63 | remainbytes -= len(line) 64 | fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line) 65 | if not fn: 66 | return (False, "Can't find out file name...") 67 | path = self.translate_path(self.path) 68 | fn = os.path.join(path, fn[0]) 69 | line = self.rfile.readline() 70 | remainbytes -= len(line) 71 | line = self.rfile.readline() 72 | remainbytes -= len(line) 73 | try: 74 | out = open(fn, 'wb') 75 | except IOError: 76 | return (False, "Can't create file to write, do you have permission to write?") 77 | 78 | preline = self.rfile.readline() 79 | remainbytes -= len(preline) 80 | while remainbytes > 0: 81 | line = self.rfile.readline() 82 | remainbytes -= len(line) 83 | if boundary in line: 84 | preline = preline[0:-1] 85 | if preline.endswith('\r'): 86 | preline = preline[0:-1] 87 | out.write(preline) 88 | out.close() 89 | return (True, "File '%s' upload success!" % fn) 90 | else: 91 | out.write(preline) 92 | preline = line 93 | return (False, "Unexpect Ends of data.") 94 | 95 | def send_head(self): 96 | """Common code for GET and HEAD commands. 97 | 98 | This sends the response code and MIME headers. 99 | 100 | Return value is either a file object (which has to be copied 101 | to the outputfile by the caller unless the command was HEAD, 102 | and must be closed by the caller under all circumstances), or 103 | None, in which case the caller has nothing further to do. 104 | 105 | """ 106 | path = self.translate_path(self.path) 107 | f = None 108 | if os.path.isdir(path): 109 | if not self.path.endswith('/'): 110 | # redirect browser - doing basically what apache does 111 | self.send_response(301) 112 | self.send_header("Location", self.path + "/") 113 | self.end_headers() 114 | return None 115 | for index in "index.html", "index.htm": 116 | index = os.path.join(path, index) 117 | if os.path.exists(index): 118 | path = index 119 | break 120 | else: 121 | return self.list_directory(path) 122 | ctype = self.guess_type(path) 123 | try: 124 | # Always read in binary mode. Opening files in text mode may cause 125 | # newline translations, making the actual size of the content 126 | # transmitted *less* than the content-length! 127 | f = open(path, 'rb') 128 | except IOError: 129 | self.send_error(404, "File not found") 130 | return None 131 | self.send_response(200) 132 | self.send_header("Content-type", ctype) 133 | fs = os.fstat(f.fileno()) 134 | self.send_header("Content-Length", str(fs[6])) 135 | self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) 136 | self.end_headers() 137 | return f 138 | 139 | def list_directory(self, path): 140 | """Helper to produce a directory listing (absent index.html). 141 | 142 | Return value is either a file object, or None (indicating an 143 | error). In either case, the headers are sent, making the 144 | interface the same as for send_head(). 145 | 146 | """ 147 | try: 148 | list = os.listdir(path) 149 | except os.error: 150 | self.send_error(404, "No permission to list directory") 151 | return None 152 | 153 | list.sort(key=lambda a: a.lower()) 154 | f = StringIO() 155 | displaypath = cgi.escape(urllib.unquote(self.path)) 156 | 157 | f.write('') 158 | f.write("\nDirectory listing for %s\n" % displaypath) 159 | f.write("\n

Directory listing for %s

\n" % displaypath) 160 | f.write("
\n") 161 | f.write("
") 162 | f.write("") 163 | f.write("
\n") 164 | f.write("
\n\n
\n\n\n") 179 | length = f.tell() 180 | f.seek(0) 181 | self.send_response(200) 182 | self.send_header("Content-type", "text/html") 183 | self.send_header("Content-Length", str(length)) 184 | self.end_headers() 185 | return f 186 | 187 | def translate_path(self, path): 188 | """Translate a /-separated PATH to the local filename syntax. 189 | 190 | Components that mean special things to the local file system 191 | (e.g. drive or directory names) are ignored. (XXX They should 192 | probably be diagnosed.) 193 | 194 | """ 195 | # abandon query parameters 196 | path = path.split('?',1)[0] 197 | path = path.split('#',1)[0] 198 | path = posixpath.normpath(urllib.unquote(path)) 199 | words = path.split('/') 200 | words = filter(None, words) 201 | path = os.getcwd() 202 | for word in words: 203 | drive, word = os.path.splitdrive(word) 204 | head, word = os.path.split(word) 205 | if word in (os.curdir, os.pardir): continue 206 | path = os.path.join(path, word) 207 | return path 208 | 209 | def copyfile(self, source, outputfile): 210 | """Copy all data between two file objects. 211 | 212 | The SOURCE argument is a file object open for reading 213 | (or anything with a read() method) and the DESTINATION 214 | argument is a file object open for writing (or 215 | anything with a write() method). 216 | 217 | The only reason for overriding this would be to change 218 | the block size or perhaps to replace newlines by CRLF 219 | -- note however that this the default server uses this 220 | to copy binary data as well. 221 | 222 | """ 223 | shutil.copyfileobj(source, outputfile) 224 | 225 | def guess_type(self, path): 226 | """Guess the type of a file. 227 | 228 | Argument is a PATH (a filename). 229 | 230 | Return value is a string of the form type/subtype, 231 | usable for a MIME Content-type header. 232 | 233 | The default implementation looks the file's extension 234 | up in the table self.extensions_map, using application/octet-stream 235 | as a default; however it would be permissible (if 236 | slow) to look inside the data to make a better guess. 237 | 238 | """ 239 | 240 | base, ext = posixpath.splitext(path) 241 | if ext in self.extensions_map: 242 | return self.extensions_map[ext] 243 | ext = ext.lower() 244 | if ext in self.extensions_map: 245 | return self.extensions_map[ext] 246 | else: 247 | return self.extensions_map[''] 248 | 249 | if not mimetypes.inited: 250 | mimetypes.init() # try to read system mime.types 251 | extensions_map = mimetypes.types_map.copy() 252 | extensions_map.update({ 253 | '': 'application/octet-stream', # Default 254 | '.py': 'text/plain', 255 | '.c': 'text/plain', 256 | '.h': 'text/plain', 257 | }) 258 | 259 | 260 | def test(HandlerClass = SimpleHTTPRequestHandler, 261 | ServerClass = BaseHTTPServer.HTTPServer): 262 | BaseHTTPServer.test(HandlerClass, ServerClass) 263 | 264 | if __name__ == '__main__': 265 | test() -------------------------------------------------------------------------------- /interface/server/wgo/basicplayer.commentbox.js: -------------------------------------------------------------------------------- 1 | (function(WGo, undefined){ 2 | 3 | "use strict"; 4 | 5 | var prepare_dom = function() { 6 | this.box = document.createElement("div"); 7 | this.box.className = "wgo-box-wrapper wgo-comments-wrapper"; 8 | this.element.appendChild(this.box); 9 | 10 | this.comments_title = document.createElement("div"); 11 | this.comments_title.className = "wgo-box-title"; 12 | this.comments_title.innerHTML = WGo.t("comments"); 13 | this.box.appendChild(this.comments_title); 14 | 15 | this.comments = document.createElement("div"); 16 | this.comments.className = "wgo-comments-content"; 17 | this.box.appendChild(this.comments); 18 | 19 | this.help = document.createElement("div"); 20 | this.help.className = "wgo-help"; 21 | this.help.style.display = "none"; 22 | this.comments.appendChild(this.help); 23 | 24 | this.notification = document.createElement("div"); 25 | this.notification.className = "wgo-notification"; 26 | this.notification.style.display = "none"; 27 | this.comments.appendChild(this.notification); 28 | 29 | this.comment_text = document.createElement("div"); 30 | this.comment_text.className = "wgo-comment-text"; 31 | this.comments.appendChild(this.comment_text); 32 | } 33 | 34 | var mark = function(move) { 35 | var x,y; 36 | 37 | x = move.charCodeAt(0)-'a'.charCodeAt(0); 38 | if(x < 0) x += 'a'.charCodeAt(0)-'A'.charCodeAt(0); 39 | if(x > 7) x--; 40 | y = (move.charCodeAt(1)-'0'.charCodeAt(0)); 41 | if(move.length > 2) y = y*10+(move.charCodeAt(2)-'0'.charCodeAt(0)); 42 | y = this.kifuReader.game.size-y; 43 | 44 | this._tmp_mark = {type:'MA', x:x, y:y}; 45 | this.board.addObject(this._tmp_mark); 46 | } 47 | 48 | var unmark = function() { 49 | this.board.removeObject(this._tmp_mark); 50 | delete this._tmp_mark; 51 | } 52 | 53 | var search_nodes = function(nodes, player) { 54 | for(var i in nodes) { 55 | if(nodes[i].className && nodes[i].className == "wgo-move-link") { 56 | nodes[i].addEventListener("mouseover", mark.bind(player, nodes[i].innerHTML)); 57 | nodes[i].addEventListener("mouseout", unmark.bind(player)); 58 | } 59 | else if(nodes[i].childNodes && nodes[i].childNodes.length) search_nodes(nodes[i].childNodes, player); 60 | } 61 | } 62 | 63 | var format_info = function(info, title) { 64 | var ret = '
'; 65 | if(title) ret += '
'+WGo.t("gameinfo")+'
'; 66 | for(var key in info) { 67 | ret += '
'+key+''+info[key]+'
'; 68 | } 69 | ret += '
'; 70 | return ret; 71 | } 72 | 73 | /** 74 | * Implements box for comments and game informations. 75 | */ 76 | 77 | var CommentBox = WGo.extendClass(WGo.BasicPlayer.component.Component, function(player) { 78 | this.super(player); 79 | this.player = player; 80 | 81 | this.element.className = "wgo-commentbox"; 82 | 83 | prepare_dom.call(this); 84 | 85 | player.addEventListener("kifuLoaded", function(e) { 86 | if(e.kifu.hasComments()) { 87 | this.comments_title.innerHTML = WGo.t("comments"); 88 | this.element.className = "wgo-commentbox"; 89 | 90 | this._update = function(e) { 91 | this.setComments(e); 92 | }.bind(this); 93 | 94 | player.addEventListener("update", this._update); 95 | } 96 | else { 97 | this.comments_title.innerHTML = WGo.t("gameinfo"); 98 | this.element.className = "wgo-commentbox wgo-gameinfo"; 99 | 100 | if(this._update) { 101 | player.removeEventListener("update", this._update); 102 | delete this._update; 103 | } 104 | this.comment_text.innerHTML = format_info(e.target.getGameInfo()); 105 | } 106 | }.bind(this)); 107 | 108 | player.notification = function(text) { 109 | if(text) { 110 | this.notification.style.display = "block"; 111 | this.notification.innerHTML = text; 112 | this.is_notification = true; 113 | } 114 | else { 115 | this.notification.style.display = "none"; 116 | this.is_notification = false; 117 | } 118 | 119 | if(this.is_notification || this.is_help) this.comment_text.style.display = "none"; 120 | else this.comment_text.style.display = "block"; 121 | 122 | }.bind(this); 123 | 124 | player.help = function(text) { 125 | if(text) { 126 | this.help.style.display = "block"; 127 | this.help.innerHTML = text; 128 | this.is_help = true; 129 | } 130 | else { 131 | this.help.style.display = "none"; 132 | this.is_help = false; 133 | } 134 | 135 | if(this.is_notification || this.is_help) this.comment_text.style.display = "none"; 136 | else this.comment_text.style.display = "block"; 137 | }.bind(this); 138 | }); 139 | 140 | CommentBox.prototype.setComments = function(e) { 141 | if(this.player._tmp_mark) unmark.call(this.player); 142 | 143 | var msg = ""; 144 | if(!e.node.parent) { 145 | msg = format_info(e.target.getGameInfo(), true); 146 | } 147 | 148 | this.comment_text.innerHTML = msg+this.getCommentText(e.node.comment, this.player.config.formatNicks, this.player.config.formatMoves); 149 | 150 | if(this.player.config.formatMoves) { 151 | if(this.comment_text.childNodes && this.comment_text.childNodes.length) search_nodes(this.comment_text.childNodes, this.player); 152 | } 153 | }; 154 | 155 | CommentBox.prototype.getCommentText = function(comment, formatNicks, formatMoves) { 156 | // to avoid XSS we must transform < and > to entities, it is highly recomanded not to change it 157 | //.replace(//g,">") : ""; 158 | if(comment) { 159 | var comm = "

"+WGo.filterHTML(comment).replace(/\n/g, "

")+"

"; 160 | if(formatNicks) comm = comm.replace(/(

)([^:]{3,}:)\s/g, '

$2 '); 161 | if(formatMoves) comm = comm.replace(/\b[a-zA-Z]1?\d\b/g, '$&'); 162 | return comm; 163 | } 164 | return ""; 165 | }; 166 | 167 | /** 168 | * Adding 2 configuration to BasicPlayer: 169 | * 170 | * - formatNicks: tries to highlight nicknames in comments (default: true) 171 | * - formatMoves: tries to highlight coordinates in comments (default: true) 172 | */ 173 | 174 | WGo.BasicPlayer.default.formatNicks = true; 175 | WGo.BasicPlayer.default.formatMoves = true; 176 | 177 | WGo.BasicPlayer.attributes["data-wgo-formatnicks"] = function(value) { 178 | if(value.toLowerCase() == "false") this.formatNicks = false; 179 | } 180 | 181 | WGo.BasicPlayer.attributes["data-wgo-formatmoves"] = function(value) { 182 | if(value.toLowerCase() == "false") this.formatMoves = false; 183 | } 184 | 185 | WGo.BasicPlayer.layouts["right_top"].right.push("CommentBox"); 186 | WGo.BasicPlayer.layouts["right"].right.push("CommentBox"); 187 | WGo.BasicPlayer.layouts["one_column"].bottom.push("CommentBox"); 188 | 189 | WGo.i18n.en["comments"] = "Comments"; 190 | WGo.i18n.en["gameinfo"] = "Game info"; 191 | 192 | WGo.BasicPlayer.component.CommentBox = CommentBox 193 | 194 | })(WGo); 195 | -------------------------------------------------------------------------------- /interface/server/wgo/basicplayer.component.js: -------------------------------------------------------------------------------- 1 | (function(WGo, undefined) { 2 | 3 | "use strict"; 4 | 5 | /** 6 | * Base class for BasicPlayer's component. Each component should implement this interface. 7 | */ 8 | 9 | var Component = function() { 10 | this.element = document.createElement("div"); 11 | } 12 | 13 | Component.prototype = { 14 | constructor: Component, 15 | 16 | /** 17 | * Append component to element. 18 | */ 19 | 20 | appendTo: function(target) { 21 | target.appendChild(this.element); 22 | }, 23 | 24 | /** 25 | * Compute and return width of component. 26 | */ 27 | 28 | getWidth: function() { 29 | var css = window.getComputedStyle(this.element); 30 | return parseInt(css.width); 31 | }, 32 | 33 | /** 34 | * Compute and return height of component. 35 | */ 36 | 37 | getHeight: function() { 38 | var css = window.getComputedStyle(this.element); 39 | return parseInt(css.height); 40 | }, 41 | 42 | /** 43 | * Update component. Actually dimensions are defined and cannot be changed in this method, 44 | * but you can change for example font size according to new dimensions. 45 | */ 46 | 47 | updateDimensions: function() { 48 | 49 | } 50 | } 51 | 52 | WGo.BasicPlayer.component.Component = Component; 53 | 54 | })(WGo); -------------------------------------------------------------------------------- /interface/server/wgo/basicplayer.control.js: -------------------------------------------------------------------------------- 1 | (function(WGo, undefined) { 2 | 3 | "use strict"; 4 | 5 | var compare_widgets = function(a,b) { 6 | if(a.weight < b.weight) return -1; 7 | else if(a.weight > b.weight) return 1; 8 | else return 0; 9 | } 10 | 11 | var prepare_dom = function(player) { 12 | 13 | this.iconBar = document.createElement("div"); 14 | this.iconBar.className = "wgo-control-wrapper"; 15 | this.element.appendChild(this.iconBar); 16 | 17 | var widget; 18 | 19 | for(var w in Control.widgets) { 20 | widget = new Control.widgets[w].constructor(player, Control.widgets[w].args); 21 | widget.appendTo(this.iconBar); 22 | this.widgets.push(widget); 23 | } 24 | } 25 | 26 | var Control = WGo.extendClass(WGo.BasicPlayer.component.Component, function(player) { 27 | this.super(player); 28 | 29 | this.widgets = []; 30 | this.element.className = "wgo-player-control"; 31 | 32 | prepare_dom.call(this, player); 33 | }); 34 | 35 | Control.prototype.updateDimensions = function() { 36 | if(this.element.clientWidth < 340) this.element.className = "wgo-player-control wgo-340"; 37 | else if(this.element.clientWidth < 440) this.element.className = "wgo-player-control wgo-440"; 38 | else this.element.className = "wgo-player-control"; 39 | } 40 | 41 | var control = WGo.BasicPlayer.control = {}; 42 | 43 | var butupd_first = function(e) { 44 | if(!e.node.parent && !this.disabled) this.disable(); 45 | else if(e.node.parent && this.disabled) this.enable(); 46 | } 47 | 48 | var butupd_last = function(e) { 49 | if(!e.node.children.length && !this.disabled) this.disable(); 50 | else if(e.node.children.length && this.disabled) this.enable(); 51 | } 52 | 53 | var but_frozen = function(e) { 54 | this._disabled = this.disabled; 55 | if(!this.disabled) this.disable(); 56 | } 57 | 58 | var but_unfrozen = function(e) { 59 | if(!this._disabled) this.enable(); 60 | delete this._disabled; 61 | } 62 | 63 | /** 64 | * Control.Widget base class. It is used for implementing buttons and other widgets. 65 | * First parameter is BasicPlayer object, second can be configuratioon object. 66 | * 67 | * args = { 68 | * name: String, // required - it is used for class name 69 | * init: Function, // other initialization code can be here 70 | * disabled: BOOLEAN, // default false 71 | * } 72 | */ 73 | 74 | control.Widget = function(player, args) { 75 | this.element = this.element || document.createElement(args.type || "div"); 76 | this.element.className = "wgo-widget-"+args.name; 77 | this.init(player, args); 78 | } 79 | 80 | control.Widget.prototype = { 81 | constructor: control.Widget, 82 | 83 | /** 84 | * Initialization function. 85 | */ 86 | 87 | init: function(player, args) { 88 | if(!args) return; 89 | if(args.disabled) this.disable(); 90 | if(args.init) args.init.call(this, player); 91 | }, 92 | 93 | /** 94 | * Append to element. 95 | */ 96 | 97 | appendTo: function(target) { 98 | target.appendChild(this.element); 99 | }, 100 | 101 | /** 102 | * Make button disabled - eventual click listener mustn't be working. 103 | */ 104 | 105 | disable: function() { 106 | this.disabled = true; 107 | if(this.element.className.search("wgo-disabled") == -1) { 108 | this.element.className += " wgo-disabled"; 109 | } 110 | }, 111 | 112 | /** 113 | * Make button working 114 | */ 115 | 116 | enable: function() { 117 | this.disabled = false; 118 | this.element.className = this.element.className.replace(" wgo-disabled",""); 119 | this.element.disabled = ""; 120 | }, 121 | 122 | } 123 | 124 | /** 125 | * Group of widgets 126 | */ 127 | 128 | control.Group = WGo.extendClass(control.Widget, function(player, args) { 129 | this.element = document.createElement("div"); 130 | this.element.className = "wgo-ctrlgroup wgo-ctrlgroup-"+args.name; 131 | 132 | var widget; 133 | for(var w in args.widgets) { 134 | widget = new args.widgets[w].constructor(player, args.widgets[w].args); 135 | widget.appendTo(this.element); 136 | } 137 | }); 138 | 139 | /** 140 | * Clickable widget - for example button. It has click action. 141 | * 142 | * args = { 143 | * title: String, // required 144 | * init: Function, // other initialization code can be here 145 | * click: Function, // required *** onclick event 146 | * togglable: BOOLEAN, // default false 147 | * selected: BOOLEAN, // default false 148 | * disabled: BOOLEAN, // default false 149 | * multiple: BOOLEAN 150 | * } 151 | */ 152 | 153 | control.Clickable = WGo.extendClass(control.Widget, function(player, args) { 154 | this.super(player, args); 155 | }); 156 | 157 | control.Clickable.prototype.init = function(player, args) { 158 | var fn, _this = this; 159 | 160 | if(args.togglable) { 161 | fn = function() { 162 | if(_this.disabled) return; 163 | 164 | if(args.click.call(_this, player)) _this.select(); 165 | else _this.unselect(); 166 | }; 167 | } 168 | else { 169 | fn = function() { 170 | if(_this.disabled) return; 171 | args.click.call(_this, player); 172 | }; 173 | } 174 | 175 | this.element.addEventListener("click", fn); 176 | this.element.addEventListener("touchstart", function(e){ 177 | e.preventDefault(); 178 | fn(); 179 | if(args.multiple) { 180 | _this._touch_i = 0; 181 | _this._touch_int = window.setInterval(function(){ 182 | if(_this._touch_i > 500) { 183 | fn(); 184 | } 185 | _this._touch_i += 100; 186 | }, 100); 187 | } 188 | return false; 189 | }); 190 | 191 | if(args.multiple) { 192 | this.element.addEventListener("touchend", function(e){ 193 | window.clearInterval(_this._touch_int); 194 | }); 195 | } 196 | 197 | if(args.disabled) this.disable(); 198 | if(args.init) args.init.call(this, player); 199 | }; 200 | 201 | control.Clickable.prototype.select = function() { 202 | this.selected = true; 203 | if(this.element.className.search("wgo-selected") == -1) this.element.className += " wgo-selected"; 204 | }; 205 | 206 | control.Clickable.prototype.unselect = function() { 207 | this.selected = false; 208 | this.element.className = this.element.className.replace(" wgo-selected",""); 209 | }; 210 | 211 | /** 212 | * Widget of button with image icon. 213 | */ 214 | 215 | control.Button = WGo.extendClass(control.Clickable, function(player, args) { 216 | var elem = this.element = document.createElement("button"); 217 | elem.className = "wgo-button wgo-button-"+args.name; 218 | elem.title = WGo.t(args.name); 219 | 220 | this.init(player, args); 221 | }); 222 | 223 | control.Button.prototype.disable = function() { 224 | control.Button.prototype.super.prototype.disable.call(this); 225 | this.element.disabled = "disabled"; 226 | } 227 | 228 | control.Button.prototype.enable = function() { 229 | control.Button.prototype.super.prototype.enable.call(this); 230 | this.element.disabled = ""; 231 | } 232 | 233 | /** 234 | * Widget used in menu 235 | */ 236 | 237 | control.MenuItem = WGo.extendClass(control.Clickable, function(player, args) { 238 | var elem = this.element = document.createElement("div"); 239 | elem.className = "wgo-menu-item wgo-menu-item-"+args.name; 240 | elem.title = WGo.t(args.name); 241 | elem.innerHTML = elem.title; 242 | 243 | this.init(player, args); 244 | }); 245 | 246 | /** 247 | * Widget for move counter. 248 | */ 249 | 250 | control.MoveNumber = WGo.extendClass(control.Widget, function(player) { 251 | this.element = document.createElement("form"); 252 | this.element.className = "wgo-player-mn-wrapper"; 253 | 254 | var move = this.move = document.createElement("input"); 255 | move.type = "text"; 256 | move.value = "0"; 257 | move.maxlength = 3; 258 | move.className = "wgo-player-mn-value"; 259 | //move.disabled = "disabled"; 260 | this.element.appendChild(move); 261 | 262 | this.element.onsubmit = move.onchange = function(player) { 263 | player.goTo(this.getValue()); 264 | return false; 265 | }.bind(this, player); 266 | 267 | player.addEventListener("update", function(e) { 268 | this.setValue(e.path.m); 269 | }.bind(this)); 270 | 271 | player.addEventListener("kifuLoaded", this.enable.bind(this)); 272 | player.addEventListener("frozen", this.disable.bind(this)); 273 | player.addEventListener("unfrozen", this.enable.bind(this)); 274 | }); 275 | 276 | control.MoveNumber.prototype.disable = function() { 277 | control.MoveNumber.prototype.super.prototype.disable.call(this); 278 | this.move.disabled = "disabled"; 279 | }; 280 | 281 | control.MoveNumber.prototype.enable = function() { 282 | control.MoveNumber.prototype.super.prototype.enable.call(this); 283 | this.move.disabled = ""; 284 | }; 285 | 286 | control.MoveNumber.prototype.setValue = function(n) { 287 | this.move.value = n; 288 | }; 289 | 290 | control.MoveNumber.prototype.getValue = function() { 291 | return parseInt(this.move.value); 292 | }; 293 | 294 | // display menu 295 | var player_menu = function(player) { 296 | 297 | if(player._menu_tmp) { 298 | delete player._menu_tmp; 299 | return; 300 | } 301 | if(!player.menu) { 302 | player.menu = document.createElement("div"); 303 | player.menu.className = "wgo-player-menu"; 304 | player.menu.style.position = "absolute"; 305 | player.menu.style.display = "none"; 306 | player.element.appendChild(player.menu); 307 | 308 | var widget; 309 | for(var i in Control.menu) { 310 | widget = new Control.menu[i].constructor(player, Control.menu[i].args, true); 311 | widget.appendTo(player.menu); 312 | } 313 | } 314 | 315 | if(player.menu.style.display != "none") { 316 | player.menu.style.display = "none"; 317 | 318 | document.removeEventListener("click", player._menu_ev); 319 | //document.removeEventListener("touchstart", player._menu_ev); 320 | delete player._menu_ev; 321 | 322 | this.unselect(); 323 | return false; 324 | } 325 | else { 326 | player.menu.style.display = "block"; 327 | 328 | var top = 0; 329 | var left = 0; 330 | var obj = this.element; 331 | 332 | while (obj && obj.tagName != 'BODY') { 333 | top += obj.offsetTop; 334 | left += obj.offsetLeft; 335 | obj = obj.offsetParent; 336 | } 337 | 338 | // kinda dirty syntax, but working well 339 | if(this.element.parentElement.parentElement.parentElement.parentElement == player.regions.bottom.wrapper) { 340 | player.menu.style.left = left+"px"; 341 | player.menu.style.top = (top-player.menu.offsetHeight+1)+"px"; 342 | } 343 | else { 344 | player.menu.style.left = left+"px"; 345 | player.menu.style.top = (top+this.element.offsetHeight)+"px"; 346 | } 347 | 348 | player._menu_ev = player_menu.bind(this, player) 349 | player._menu_tmp = true; 350 | 351 | document.addEventListener("click", player._menu_ev); 352 | //document.addEventListener("touchstart", player._menu_ev); 353 | 354 | return true; 355 | } 356 | } 357 | 358 | /** 359 | * List of widgets (probably MenuItem objects) to be displayed in drop-down menu. 360 | */ 361 | 362 | Control.menu = [{ 363 | constructor: control.MenuItem, 364 | args: { 365 | name: "switch-coo", 366 | togglable: true, 367 | click: function(player) { 368 | player.setCoordinates(!player.coordinates); 369 | return player.coordinates; 370 | }, 371 | init: function(player) { 372 | if(player.coordinates) this.select(); 373 | } 374 | } 375 | }]; 376 | 377 | /** 378 | * List of widgets (probably Button objects) to be displayed. 379 | * 380 | * widget = { 381 | * constructor: Function, // construct a instance of widget 382 | * args: Object, 383 | * } 384 | */ 385 | 386 | Control.widgets = [ { 387 | constructor: control.Group, 388 | args: { 389 | name: "left", 390 | widgets: [{ 391 | constructor: control.Button, 392 | args: { 393 | name: "menu", 394 | togglable: true, 395 | click: player_menu, 396 | } 397 | }] 398 | } 399 | }, { 400 | constructor: control.Group, 401 | args: { 402 | name: "right", 403 | widgets: [{ 404 | constructor: control.Button, 405 | args: { 406 | name: "about", 407 | click: function(player) { 408 | player.showMessage(WGo.t("about-text")); 409 | }, 410 | } 411 | }] 412 | } 413 | }, { 414 | constructor: control.Group, 415 | args: { 416 | name: "control", 417 | widgets: [{ 418 | constructor: control.Button, 419 | args: { 420 | name: "first", 421 | disabled: true, 422 | init: function(player) { 423 | player.addEventListener("update", butupd_first.bind(this)); 424 | player.addEventListener("frozen", but_frozen.bind(this)); 425 | player.addEventListener("unfrozen", but_unfrozen.bind(this)); 426 | }, 427 | click: function(player) { 428 | player.first(); 429 | }, 430 | } 431 | }, { 432 | constructor: control.Button, 433 | args: { 434 | name: "multiprev", 435 | disabled: true, 436 | multiple: true, 437 | init: function(player) { 438 | player.addEventListener("update", butupd_first.bind(this)); 439 | player.addEventListener("frozen", but_frozen.bind(this)); 440 | player.addEventListener("unfrozen", but_unfrozen.bind(this)); 441 | }, 442 | click: function(player) { 443 | var p = WGo.clone(player.kifuReader.path); 444 | p.m -= 10; 445 | player.goTo(p); 446 | }, 447 | } 448 | },{ 449 | constructor: control.Button, 450 | args: { 451 | name: "previous", 452 | disabled: true, 453 | multiple: true, 454 | init: function(player) { 455 | player.addEventListener("update", butupd_first.bind(this)); 456 | player.addEventListener("frozen", but_frozen.bind(this)); 457 | player.addEventListener("unfrozen", but_unfrozen.bind(this)); 458 | }, 459 | click: function(player) { 460 | player.previous(); 461 | }, 462 | } 463 | }, { 464 | constructor: control.MoveNumber, 465 | }, { 466 | constructor: control.Button, 467 | args: { 468 | name: "next", 469 | disabled: true, 470 | multiple: true, 471 | init: function(player) { 472 | player.addEventListener("update", butupd_last.bind(this)); 473 | player.addEventListener("frozen", but_frozen.bind(this)); 474 | player.addEventListener("unfrozen", but_unfrozen.bind(this)); 475 | }, 476 | click: function(player) { 477 | player.next(); 478 | }, 479 | } 480 | }, { 481 | constructor: control.Button, 482 | args: { 483 | name: "multinext", 484 | disabled: true, 485 | multiple: true, 486 | init: function(player) { 487 | player.addEventListener("update", butupd_last.bind(this)); 488 | player.addEventListener("frozen", but_frozen.bind(this)); 489 | player.addEventListener("unfrozen", but_unfrozen.bind(this)); 490 | }, 491 | click: function(player) { 492 | var p = WGo.clone(player.kifuReader.path); 493 | p.m += 10; 494 | player.goTo(p); 495 | }, 496 | } 497 | }, { 498 | constructor: control.Button, 499 | args: { 500 | name: "last", 501 | disabled: true, 502 | init: function(player) { 503 | player.addEventListener("update", butupd_last.bind(this)); 504 | player.addEventListener("frozen", but_frozen.bind(this)); 505 | player.addEventListener("unfrozen", but_unfrozen.bind(this)); 506 | }, 507 | click: function(player) { 508 | player.last() 509 | }, 510 | } 511 | }] 512 | } 513 | }]; 514 | 515 | var bp_layouts = WGo.BasicPlayer.layouts; 516 | bp_layouts["right_top"].top.push("Control"); 517 | bp_layouts["right"].right.push("Control"); 518 | bp_layouts["one_column"].top.push("Control"); 519 | bp_layouts["no_comment"].bottom.push("Control"); 520 | bp_layouts["minimal"].bottom.push("Control"); 521 | 522 | var player_terms = { 523 | "about": "About", 524 | "first": "First", 525 | "multiprev": "10 moves back", 526 | "previous": "Previous", 527 | "next": "Next", 528 | "multinext": "10 moves forward", 529 | "last": "Last", 530 | "switch-coo": "Display coordinates", 531 | "menu": "Menu", 532 | }; 533 | 534 | for(var key in player_terms) WGo.i18n.en[key] = player_terms[key]; 535 | 536 | WGo.BasicPlayer.component.Control = Control; 537 | 538 | })(WGo); 539 | -------------------------------------------------------------------------------- /interface/server/wgo/basicplayer.infobox.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | "use strict"; 4 | 5 | var prepare_dom = function() { 6 | prepare_dom_box.call(this,"white"); 7 | prepare_dom_box.call(this,"black"); 8 | this.element.appendChild(this.white.box); 9 | this.element.appendChild(this.black.box); 10 | } 11 | 12 | var prepare_dom_box = function(type) { 13 | this[type] = {}; 14 | var t = this[type]; 15 | t.box = document.createElement("div"); 16 | t.box.className = "wgo-box-wrapper wgo-player-wrapper wgo-"+type; 17 | 18 | t.name = document.createElement("div"); 19 | t.name.className = "wgo-box-title"; 20 | t.name.innerHTML = type; 21 | t.box.appendChild(t.name); 22 | 23 | var info_wrapper; 24 | info_wrapper = document.createElement("div"); 25 | info_wrapper.className = "wgo-player-info"; 26 | t.box.appendChild(info_wrapper); 27 | 28 | t.info = {}; 29 | t.info.rank = prepare_dom_info("rank"); 30 | t.info.rank.val.innerHTML = "-"; 31 | t.info.caps = prepare_dom_info("caps"); 32 | t.info.caps.val.innerHTML = "0"; 33 | t.info.time = prepare_dom_info("time"); 34 | t.info.time.val.innerHTML = "--:--"; 35 | info_wrapper.appendChild(t.info.rank.wrapper); 36 | info_wrapper.appendChild(t.info.caps.wrapper); 37 | info_wrapper.appendChild(t.info.time.wrapper); 38 | } 39 | 40 | var prepare_dom_info = function(type) { 41 | var res = {}; 42 | res.wrapper = document.createElement("div"); 43 | res.wrapper.className = "wgo-player-info-box-wrapper"; 44 | 45 | res.box = document.createElement("div"); 46 | res.box.className = "wgo-player-info-box"; 47 | res.wrapper.appendChild(res.box); 48 | 49 | res.title = document.createElement("div"); 50 | res.title.className = "wgo-player-info-title"; 51 | res.title.innerHTML = WGo.t(type); 52 | res.box.appendChild(res.title); 53 | 54 | res.val = document.createElement("div"); 55 | res.val.className = "wgo-player-info-value"; 56 | res.box.appendChild(res.val); 57 | 58 | return res; 59 | } 60 | 61 | var kifu_loaded = function(e) { 62 | var info = e.kifu.info || {}; 63 | 64 | if(info.black) { 65 | this.black.name.innerHTML = WGo.filterHTML(info.black.name) || WGo.t("Black"); 66 | this.black.info.rank.val.innerHTML = WGo.filterHTML(info.black.rank) || "-"; 67 | } 68 | else { 69 | this.black.name.innerHTML = WGo.t("Black"); 70 | this.black.info.rank.val.innerHTML = "-"; 71 | } 72 | if(info.white) { 73 | this.white.name.innerHTML = WGo.filterHTML(info.white.name) || WGo.t("White"); 74 | this.white.info.rank.val.innerHTML = WGo.filterHTML(info.white.rank) || "-"; 75 | } 76 | else { 77 | this.white.name.innerHTML = WGo.t("White"); 78 | this.white.info.rank.val.innerHTML = "-"; 79 | } 80 | 81 | this.black.info.caps.val.innerHTML = "0"; 82 | this.white.info.caps.val.innerHTML = "0"; 83 | 84 | if(info.TM) { 85 | this.setPlayerTime("black", info.TM); 86 | this.setPlayerTime("white", info.TM); 87 | } 88 | else { 89 | this.black.info.time.val.innerHTML = "--:--"; 90 | this.white.info.time.val.innerHTML = "--:--"; 91 | } 92 | 93 | this.updateDimensions(); 94 | } 95 | 96 | var modify_font_size = function(elem) { 97 | var css, max, size; 98 | 99 | if(elem.style.fontSize) { 100 | var size = parseInt(elem.style.fontSize); 101 | elem.style.fontSize = ""; 102 | css = window.getComputedStyle(elem); 103 | max = parseInt(css.fontSize); 104 | elem.style.fontSize = size+"px"; 105 | } 106 | else { 107 | css = window.getComputedStyle(elem); 108 | max = size = parseInt(css.fontSize); 109 | } 110 | 111 | if(size == max && elem.scrollHeight <= elem.offsetHeight) return; 112 | else if(elem.scrollHeight > elem.offsetHeight) { 113 | size -= 2; 114 | while(elem.scrollHeight > elem.offsetHeight && size > 1) { 115 | elem.style.fontSize = size+"px"; 116 | size -= 2; 117 | } 118 | } 119 | else if(size < max) { 120 | size += 2; 121 | while(elem.scrollHeight <= elem.offsetHeight && size <= max) { 122 | elem.style.fontSize = size+"px"; 123 | size += 2; 124 | } 125 | if(elem.scrollHeight > elem.offsetHeight) { 126 | elem.style.fontSize = (size-4)+"px"; 127 | } 128 | } 129 | } 130 | 131 | var update = function(e) { 132 | if(e.node.BL) this.setPlayerTime("black", e.node.BL); 133 | if(e.node.WL) this.setPlayerTime("white", e.node.WL); 134 | if(e.position.capCount.black !== undefined) this.black.info.caps.val.innerHTML = e.position.capCount.black; 135 | if(e.position.capCount.white !== undefined) this.white.info.caps.val.innerHTML = e.position.capCount.white; 136 | } 137 | 138 | /** 139 | * Implements box with basic informations about go players. 140 | */ 141 | 142 | var InfoBox = WGo.extendClass(WGo.BasicPlayer.component.Component, function(player) { 143 | this.super(player); 144 | this.element.className = "wgo-infobox"; 145 | 146 | prepare_dom.call(this); 147 | 148 | player.addEventListener("kifuLoaded", kifu_loaded.bind(this)); 149 | player.addEventListener("update", update.bind(this)); 150 | 151 | }); 152 | 153 | InfoBox.prototype.setPlayerTime = function(color, time) { 154 | var min = Math.floor(time/60); 155 | var sec = Math.round(time)%60; 156 | this[color].info.time.val.innerHTML = min+":"+((sec < 10) ? "0"+sec : sec); 157 | }; 158 | 159 | InfoBox.prototype.updateDimensions = function() { 160 | modify_font_size(this.black.name); 161 | modify_font_size(this.white.name); 162 | }; 163 | 164 | var bp_layouts = WGo.BasicPlayer.layouts; 165 | bp_layouts["right_top"].right.push("InfoBox"); 166 | bp_layouts["right"].right.push("InfoBox"); 167 | bp_layouts["one_column"].top.push("InfoBox"); 168 | bp_layouts["no_comment"].top.push("InfoBox"); 169 | 170 | WGo.i18n.en["rank"] = "Rank"; 171 | WGo.i18n.en["caps"] = "Caps"; 172 | WGo.i18n.en["time"] = "Time"; 173 | 174 | WGo.BasicPlayer.component.InfoBox = InfoBox; 175 | 176 | })(WGo); 177 | -------------------------------------------------------------------------------- /interface/server/wgo/basicplayer.js: -------------------------------------------------------------------------------- 1 | 2 | (function(WGo){ 3 | 4 | "use strict"; 5 | 6 | // player counter - for creating unique ids 7 | var pl_count = 0; 8 | 9 | // generate DOM of region 10 | var playerBlock = function(name, parent, visible) { 11 | var e = {}; 12 | e.element = document.createElement("div"); 13 | e.element.className = "wgo-player-"+name; 14 | e.wrapper = document.createElement("div"); 15 | e.wrapper.className = "wgo-player-"+name+"-wrapper"; 16 | e.element.appendChild(e.wrapper); 17 | parent.appendChild(e.element); 18 | if(!visible) e.element.style.display = "none"; 19 | return e; 20 | } 21 | 22 | // generate all DOM of player 23 | var BPgenerateDom = function() { 24 | // wrapper object for common DOM 25 | this.dom = {}; 26 | 27 | // center element 28 | this.dom.center = document.createElement("div"); 29 | this.dom.center.className = "wgo-player-center"; 30 | 31 | // board wrapper element 32 | this.dom.board = document.createElement("div"); 33 | this.dom.board.className = "wgo-player-board"; 34 | 35 | // object wrapper for regions (left, right, top, bottom) 36 | this.regions = {}; 37 | 38 | /* 39 | pseudo DOM structure: 40 |

41 | 42 |
43 | 44 | 45 | 46 |
47 | 48 |
49 | */ 50 | 51 | this.regions.left = playerBlock("left", this.element); 52 | this.element.appendChild(this.dom.center); 53 | this.regions.right = playerBlock("right", this.element); 54 | 55 | this.regions.top = playerBlock("top", this.dom.center); 56 | this.dom.center.appendChild(this.dom.board); 57 | this.regions.bottom = playerBlock("bottom", this.dom.center); 58 | } 59 | 60 | var getCurrentLayout = function() { 61 | var cl = this.config.layout; 62 | if(cl.constructor != Array) return cl; 63 | 64 | var bh = this.height || this.maxHeight; 65 | for(var i = 0; i < cl.length; i++) { 66 | 67 | if(!cl[i].conditions || ( 68 | (!cl[i].conditions.minWidth || cl[i].conditions.minWidth <= this.width) && 69 | (!cl[i].conditions.minHeight || !bh || cl[i].conditions.minHeight <= bh) && 70 | (!cl[i].conditions.maxWidth || cl[i].conditions.maxWidth >= this.width) && 71 | (!cl[i].conditions.maxHeight || !bh || cl[i].conditions.maxHeight >= bh) && 72 | (!cl[i].conditions.custom || cl[i].conditions.custom.call(this)) 73 | )) { 74 | return cl[i]; 75 | } 76 | } 77 | } 78 | 79 | var appendComponents = function(area) { 80 | var components; 81 | 82 | if(this.currentLayout.layout) components = this.currentLayout.layout[area]; 83 | else components = this.currentLayout[area]; 84 | 85 | if(components) { 86 | this.regions[area].element.style.display = "block"; 87 | 88 | for(var i in components) { 89 | if(!this.components[components[i]]) this.components[components[i]] = new BasicPlayer.component[components[i]](this); 90 | 91 | this.components[components[i]].appendTo(this.regions[area].wrapper); 92 | 93 | // remove detach flag 94 | this.components[components[i]]._detachFromPlayer = false; 95 | } 96 | } 97 | else { 98 | this.regions[area].element.style.display = "none"; 99 | } 100 | 101 | } 102 | 103 | var manageComponents = function() { 104 | // add detach flags to every widget 105 | for(var key in this.components) { 106 | this.components[key]._detachFromPlayer = true; 107 | } 108 | 109 | appendComponents.call(this, "left"); 110 | appendComponents.call(this, "right"); 111 | appendComponents.call(this, "top"); 112 | appendComponents.call(this, "bottom"); 113 | 114 | // detach all invisible components 115 | for(var key in this.components) { 116 | if(this.components[key]._detachFromPlayer && this.components[key].element.parentNode) this.components[key].element.parentNode.removeChild(this.components[key].element); 117 | } 118 | } 119 | 120 | /** 121 | * Main object of player, it binds all magic together and produces visible player. 122 | * It inherits some functionality from WGo.PlayerView, but full html structure is done here. 123 | * 124 | * Layout of player can be set. It can be even dynamic according to screen resolution. 125 | * There are 5 areas - left, right, top and bottom, and there is special region for board. 126 | * You can put BasicPlayer.Component objects to these regions. Basic components are: 127 | * - BasicPlayer.CommentBox - box with comments and game informations 128 | * - BasicPlayer.InfoBox - box with information about players 129 | * - BasicPlayer.Control - buttons and staff for control 130 | * 131 | * Possible configurations: 132 | * - sgf: sgf string (default: undefined) 133 | * - json: kifu stored in json/jgo (default: undefined) 134 | * - sgfFile: sgf file path (default: undefined) 135 | * - board: configuration object of board (default: {}) 136 | * - enableWheel: allow player to be controlled by mouse wheel (default: true) 137 | * - lockScroll: disable window scrolling while hovering player (default: true) 138 | * - enableKeys: allow player to be controlled by arrow keys (default: true) 139 | * - kifuLoaded: extra Player's kifuLoaded event listener (default: undefined) 140 | * - update: extra Player's update event listener (default: undefined) 141 | * - frozen: extra Player's frozen event listener (default: undefined) 142 | * - unfrozen: extra Player's unfrozen event listener (default: undefined) 143 | * - layout: layout object. Look below how to define your own layout (default: BasicPlayer.dynamicLayout) 144 | * 145 | * You also must specify main DOMElement of player. 146 | */ 147 | 148 | var BasicPlayer = WGo.extendClass(WGo.Player, function(elem, config) { 149 | this.config = config; 150 | 151 | // add default configuration of BasicPlayer 152 | for(var key in BasicPlayer.default) if(this.config[key] === undefined && BasicPlayer.default[key] !== undefined) this.config[key] = BasicPlayer.default[key]; 153 | // add default configuration of Player class 154 | for(var key in WGo.Player.default) if(this.config[key] === undefined && WGo.Player.default[key] !== undefined) this.config[key] = WGo.Player.default[key]; 155 | 156 | this.element = elem 157 | this.element.innerHTML = ""; 158 | this.classes = (this.element.className ? this.element.className+" " : "")+"wgo-player-main" ; 159 | this.element.className = this.classes; 160 | if(!this.element.id) this.element.id = "wgo_"+(pl_count++); 161 | 162 | BPgenerateDom.call(this); 163 | 164 | this.board = new WGo.Board(this.dom.board, this.config.board); 165 | 166 | this.init(); 167 | 168 | this.components = {}; 169 | 170 | window.addEventListener("resize", function() { 171 | if(!this.noresize) { 172 | this.updateDimensions(); 173 | } 174 | 175 | }.bind(this)); 176 | 177 | this.updateDimensions(); 178 | 179 | this.initGame(); 180 | }); 181 | 182 | /** 183 | * Append player to different element. 184 | */ 185 | 186 | BasicPlayer.prototype.appendTo = function(elem) { 187 | elem.appendChild(this.element); 188 | this.updateDimensions(); 189 | } 190 | 191 | /** 192 | * Set right dimensions of all elements. 193 | */ 194 | 195 | BasicPlayer.prototype.updateDimensions = function() { 196 | var css = window.getComputedStyle(this.element); 197 | 198 | var els = []; 199 | while(this.element.firstChild) { 200 | els.push(this.element.firstChild); 201 | this.element.removeChild(this.element.firstChild); 202 | } 203 | 204 | var tmp_w = parseInt(css.width); 205 | var tmp_h = parseInt(css.height); 206 | var tmp_mh = parseInt(css.maxHeight) || 0; 207 | 208 | for(var i = 0; i < els.length; i++) { 209 | this.element.appendChild(els[i]); 210 | } 211 | 212 | if(tmp_w == this.width && tmp_h == this.height && tmp_mh == this.maxHeight) return; 213 | 214 | this.width = tmp_w; 215 | this.height = tmp_h; 216 | this.maxHeight = tmp_mh; 217 | 218 | this.currentLayout = getCurrentLayout.call(this); 219 | 220 | if(this.currentLayout && this.lastLayout != this.currentLayout) { 221 | if(this.currentLayout.className) this.element.className = this.classes+" "+this.currentLayout.className; 222 | else this.element.className = this.classes; 223 | manageComponents.call(this); 224 | this.lastLayout = this.currentLayout; 225 | } 226 | 227 | //var bw = this.width - this.regions.left.element.clientWidth - this.regions.right.element.clientWidth; 228 | var bw = this.dom.board.clientWidth; 229 | var bh = this.height || this.maxHeight; 230 | 231 | if(bh) { 232 | bh -= this.regions.top.element.offsetHeight + this.regions.bottom.element.offsetHeight; 233 | } 234 | 235 | if(bh && bh < bw) { 236 | if(bh != this.board.height) this.board.setHeight(bh); 237 | } 238 | else { 239 | if(bw != this.board.width) this.board.setWidth(bw); 240 | } 241 | 242 | var diff = bh - bw; 243 | 244 | if(diff > 0) { 245 | this.dom.board.style.height = bh+"px"; 246 | this.dom.board.style.paddingTop = (diff/2)+"px"; 247 | } 248 | else { 249 | this.dom.board.style.height = "auto"; 250 | this.dom.board.style.paddingTop = "0"; 251 | } 252 | 253 | this.regions.left.element.style.height = this.dom.center.offsetHeight+"px"; 254 | this.regions.right.element.style.height = this.dom.center.offsetHeight+"px"; 255 | 256 | for(var i in this.components) { 257 | if(this.components[i].updateDimensions) this.components[i].updateDimensions(); 258 | } 259 | } 260 | 261 | /** 262 | * Layout contains built-in info box, for displaying of text(html) messages. 263 | * You can use this method to display a message. 264 | * 265 | * @param text or html to display 266 | * @param closeCallback optional callback function which is called when message is closed 267 | */ 268 | 269 | BasicPlayer.prototype.showMessage = function(text, closeCallback, permanent) { 270 | this.info_overlay = document.createElement("div"); 271 | this.info_overlay.style.width = this.element.offsetWidth+"px"; 272 | this.info_overlay.style.height = this.element.offsetHeight+"px"; 273 | this.info_overlay.className = "wgo-info-overlay"; 274 | this.element.appendChild(this.info_overlay); 275 | 276 | var info_message = document.createElement("div"); 277 | info_message.className = "wgo-info-message"; 278 | info_message.innerHTML = text; 279 | 280 | var close_info = document.createElement("div"); 281 | close_info.className = "wgo-info-close"; 282 | if(!permanent) close_info.innerHTML = WGo.t("BP:closemsg"); 283 | 284 | info_message.appendChild(close_info); 285 | 286 | this.info_overlay.appendChild(info_message); 287 | 288 | if(closeCallback) { 289 | this.info_overlay.addEventListener("click",function(e) { 290 | closeCallback(e); 291 | }); 292 | } 293 | else if(!permanent) { 294 | this.info_overlay.addEventListener("click",function(e) { 295 | this.hideMessage(); 296 | }.bind(this)); 297 | } 298 | 299 | this.setFrozen(true); 300 | } 301 | 302 | /** 303 | * Hide a message box. 304 | */ 305 | 306 | BasicPlayer.prototype.hideMessage = function() { 307 | this.element.removeChild(this.info_overlay); 308 | this.setFrozen(false); 309 | } 310 | 311 | /** 312 | * Error handling 313 | */ 314 | 315 | BasicPlayer.prototype.error = function(err) { 316 | if(!WGo.ERROR_REPORT) throw err; 317 | 318 | var url = "#"; 319 | 320 | switch(err.name) { 321 | case "InvalidMoveError": 322 | this.showMessage("

"+err.name+"

"+err.message+"

If this message isn't correct, please report it by clicking here, otherwise contact maintainer of this site.

"); 323 | break; 324 | case "FileError": 325 | this.showMessage("

"+err.name+"

"+err.message+"

Please contact maintainer of this site. Note: it is possible to read files only from this host.

"); 326 | break; 327 | default: 328 | this.showMessage("

"+err.name+"

"+err.message+"

"+err.stacktrace+"

Please contact maintainer of this site. You can also report it here.

"); 329 | } 330 | } 331 | 332 | BasicPlayer.component = {}; 333 | 334 | /** 335 | * Preset layouts 336 | * They have defined regions as arrays, which can contain components. For each of these layouts each component specifies where it is placed. 337 | * You can create your own layout in same manners, but you must specify components manually. 338 | */ 339 | 340 | BasicPlayer.layouts = { 341 | "one_column": { 342 | top: [], 343 | bottom: [], 344 | }, 345 | "no_comment": { 346 | top: [], 347 | bottom: [], 348 | }, 349 | "right_top": { 350 | top: [], 351 | right: [], 352 | }, 353 | "right": { 354 | right: [], 355 | }, 356 | "minimal": { 357 | bottom: [] 358 | }, 359 | }; 360 | 361 | /** 362 | * WGo player can have more layouts. It allows responsive design of the player. 363 | * Possible layouts are defined as array of object with this structure: 364 | * 365 | * layout = { 366 | * Object layout, // layout as specified above 367 | * Object conditions, // conditions that has to be valid to apply this layout 368 | * String className // custom classnames 369 | * } 370 | * 371 | * possible conditions: 372 | * - minWidth - minimal width of player in px 373 | * - maxWidth - maximal width of player in px 374 | * - minHeight - minimal height of player in px 375 | * - maxHeight - maximal height of player in px 376 | * - custom - function which is called in template context, must return true or false 377 | * 378 | * Player's template evaluates layouts step by step and first layout that matches the conditions is applied. 379 | * 380 | * Look below at the default dynamic layout. Layouts are tested after every window resize. 381 | */ 382 | 383 | BasicPlayer.dynamicLayout = [ 384 | { 385 | conditions: { 386 | minWidth: 650, 387 | }, 388 | layout: BasicPlayer.layouts["right_top"], 389 | className: "wgo-twocols wgo-large", 390 | }, 391 | { 392 | conditions: { 393 | minWidth: 550, 394 | minHeight: 600, 395 | }, 396 | layout: BasicPlayer.layouts["one_column"], 397 | className: "wgo-medium" 398 | }, 399 | { 400 | conditions: { 401 | minWidth: 350, 402 | }, 403 | layout: BasicPlayer.layouts["no_comment"], 404 | className: "wgo-small" 405 | }, 406 | { // if conditions object is omitted, layout is applied 407 | layout: BasicPlayer.layouts["no_comment"], 408 | className: "wgo-xsmall", 409 | }, 410 | ]; 411 | 412 | // default settings, they are merged with user settings in constructor. 413 | BasicPlayer.default = { 414 | layout: BasicPlayer.dynamicLayout, 415 | } 416 | 417 | WGo.i18n.en["BP:closemsg"] = "click anywhere to close this window"; 418 | 419 | //--- Handling
with HTML5 data attributes ----------------------------------------------------------------- 420 | 421 | BasicPlayer.attributes = { 422 | "data-wgo": function(value) { 423 | if(value) { 424 | if(value[0] == "(") this.sgf = value; 425 | else this.sgfFile = value; 426 | } 427 | }, 428 | 429 | "data-wgo-board": function(value) { 430 | // using eval to parse strings like "stoneStyle: 'painted'" 431 | this.board = eval("({"+value+"})"); 432 | }, 433 | 434 | "data-wgo-onkifuload": function(value) { 435 | this.kifuLoaded = new Function(value); 436 | }, 437 | 438 | "data-wgo-onupdate": function(value) { 439 | this.update = new Function(value); 440 | }, 441 | 442 | "data-wgo-onfrozen": function(value) { 443 | this.frozen = new Function(value); 444 | }, 445 | 446 | "data-wgo-onunfrozen": function(value) { 447 | this.unfrozen = new Function(value); 448 | }, 449 | 450 | "data-wgo-layout": function(value) { 451 | this.layout = eval("({"+value+"})"); 452 | }, 453 | 454 | "data-wgo-enablewheel": function(value) { 455 | if(value.toLowerCase() == "false") this.enableWheel = false; 456 | }, 457 | 458 | "data-wgo-lockscroll": function(value) { 459 | if(value.toLowerCase() == "false") this.lockScroll = false; 460 | }, 461 | 462 | "data-wgo-enablekeys": function(value) { 463 | if(value.toLowerCase() == "false") this.enableKeys = false; 464 | }, 465 | 466 | "data-wgo-rememberpath": function(value) { 467 | if(value.toLowerCase() == "false") this.rememberPath = false; 468 | }, 469 | 470 | "data-wgo-move": function(value) { 471 | var m = parseInt(value); 472 | if(m) this.move = m; 473 | else this.move = eval("({"+value+"})"); 474 | }, 475 | } 476 | 477 | var player_from_tag = function(elem) { 478 | var att, config, pl; 479 | 480 | config = {}; 481 | 482 | for(var a = 0; a < elem.attributes.length; a++) { 483 | att = elem.attributes[a]; 484 | if(BasicPlayer.attributes[att.name]) BasicPlayer.attributes[att.name].call(config, att.value, att.name); 485 | } 486 | 487 | pl = new BasicPlayer(elem, config); 488 | elem._wgo_player = pl; 489 | } 490 | 491 | WGo.BasicPlayer = BasicPlayer; 492 | 493 | window.addEventListener("load", function() { 494 | var pl_elems = document.querySelectorAll("[data-wgo]"); 495 | 496 | for(var i = 0; i < pl_elems.length; i++) { 497 | player_from_tag(pl_elems[i]); 498 | } 499 | }); 500 | 501 | })(WGo); 502 | -------------------------------------------------------------------------------- /interface/server/wgo/kifu.js: -------------------------------------------------------------------------------- 1 |  2 | /** 3 | * This extension handles go game records(kifu). In WGo kifu is stored in JSON. Kifu structure example: 4 | * 5 | * JGO proposal = { 6 | * size: 19, 7 | * info: { 8 | * black: {name:"Lee Chang-Ho", rank:"9p"}, 9 | * white: {name:"Lee Sedol", rank:"9p"}, 10 | * komi: 6.5, 11 | * }, 12 | * game: [ 13 | * {B:"mm"}, 14 | * {W:"nn"}, 15 | * {B:"cd"}, 16 | * {}, 17 | * ] 18 | * } 19 | * 20 | */ 21 | 22 | (function (WGo, undefined) { 23 | 24 | "use strict"; 25 | 26 | var recursive_clone = function(node) { 27 | var n = new KNode(JSON.parse(JSON.stringify(node.getProperties()))); 28 | for(var ch in node.children) { 29 | n.appendChild(recursive_clone(node.children[ch])); 30 | } 31 | return n; 32 | } 33 | 34 | var find_property = function(prop, node) { 35 | var res; 36 | if(node[prop] !== undefined) return node[prop]; 37 | for(var ch in node.children) { 38 | res = find_property(prop, node.children[ch]) 39 | if(res) return res; 40 | } 41 | return false; 42 | } 43 | 44 | var recursive_save = function(gameTree, node) { 45 | gameTree.push(JSON.parse(JSON.stringify(node.getProperties()))); 46 | if(node.children.length > 1) { 47 | var nt = []; 48 | for(var i = 0; i < node.children.length; i++) { 49 | var t = []; 50 | recursive_save(t, node.children[i]); 51 | nt.push(t); 52 | } 53 | gameTree.push(nt); 54 | } 55 | else if(node.children.length) { 56 | recursive_save(gameTree, node.children[0]); 57 | } 58 | } 59 | 60 | var recursive_save2 = function(gameTree, node) { 61 | var anode = node; 62 | var tnode; 63 | 64 | for(var i = 1; i < gameTree.length; i++) { 65 | if(gameTree[i].constructor == Array) { 66 | for(var j = 0; j < gameTree[i].length; j++) { 67 | tnode = new KNode(gameTree[i][j][0]); 68 | anode.appendChild(tnode); 69 | recursive_save2(gameTree[i][j], tnode); 70 | } 71 | } 72 | else { 73 | tnode = new KNode(gameTree[i]); 74 | anode.insertAfter(tnode); 75 | anode = tnode; 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * Kifu class - for storing go game record and easy manipulation with it 82 | */ 83 | 84 | var Kifu = function() { 85 | this.size = 19; 86 | this.info = {}; 87 | this.root = new KNode(); 88 | this.nodeCount = 0; 89 | this.propertyCount = 0; 90 | } 91 | 92 | Kifu.prototype ={ 93 | constructor: Kifu, 94 | clone: function() { 95 | var clone = new Kifu(); 96 | clone.size = this.size; 97 | clone.info = JSON.parse(JSON.stringify(this.info)); 98 | clone.root = recursive_clone(this.root); 99 | clone.nodeCount = this.nodeCount; 100 | clone.propertyCount = this.propertyCount; 101 | return clone; 102 | }, 103 | hasComments: function() { 104 | return !!find_property("comment", this.root); 105 | }, 106 | } 107 | 108 | /** 109 | * Create kifu object from SGF string 110 | */ 111 | 112 | Kifu.fromSgf = function(sgf) { 113 | return WGo.SGF.parse(sgf); 114 | } 115 | 116 | /** 117 | * Create kifu object from JGO 118 | */ 119 | 120 | Kifu.fromJGO = function(arg) { 121 | var jgo = typeof arg == "string" ? JSON.parse(arg) : arg; 122 | var kifu = new Kifu(); 123 | kifu.info = JSON.parse(JSON.stringify(jgo.info)); 124 | kifu.size = jgo.size; 125 | kifu.nodeCount = jgo.nodeCount; 126 | kifu.propertyCount = jgo.propertyCount; 127 | 128 | kifu.root = new KNode(jgo.game[0]); 129 | recursive_save2(jgo.game, kifu.root); 130 | 131 | return kifu; 132 | } 133 | 134 | /** 135 | * Return SGF string from kifu object 136 | */ 137 | 138 | Kifu.prototype.toSgf = function() { 139 | // not implemented yet 140 | } 141 | 142 | /** 143 | * Return JGO from kifu object 144 | */ 145 | 146 | Kifu.prototype.toJGO = function(stringify) { 147 | var jgo = {}; 148 | jgo.size = this.size; 149 | jgo.info = JSON.parse(JSON.stringify(this.info)); 150 | jgo.nodeCount = this.nodeCount; 151 | jgo.propertyCount = this.propertyCount; 152 | jgo.game = []; 153 | recursive_save(jgo.game, this.root); 154 | if(stringify) return JSON.stringify(jgo); 155 | else return jgo; 156 | } 157 | 158 | var player_formatter = function(value) { 159 | var str; 160 | if(value.name) { 161 | str = WGo.filterHTML(value.name); 162 | if(value.rank) str += " ("+WGo.filterHTML(value.rank)+")"; 163 | if(value.team) str += ", "+WGo.filterHTML(value.team); 164 | } 165 | else { 166 | if(value.team) str = WGo.filterHTML(value.team); 167 | if(value.rank) str += " ("+WGo.filterHTML(value.rank)+")"; 168 | } 169 | return str; 170 | } 171 | 172 | /** 173 | * Game information formatters. Each formatter is a function which somehow formats input text. 174 | */ 175 | 176 | Kifu.infoFormatters = { 177 | black: player_formatter, 178 | white: player_formatter, 179 | TM: function(time) { 180 | if(time == 0) return WGo.t("none"); 181 | 182 | var res, t = Math.floor(time/60); 183 | 184 | if(t == 1) res = "1 "+WGo.t("minute"); 185 | else if(t > 1) res = t+" "+WGo.t("minutes"); 186 | 187 | t = time%60; 188 | if(t == 1) res += " 1 "+WGo.t("second"); 189 | else if(t > 1) res += " "+t+" "+WGo.t("seconds"); 190 | 191 | return res; 192 | }, 193 | RE: function(res) { 194 | return ''+WGo.t('show')+''; 195 | }, 196 | } 197 | 198 | /** 199 | * List of game information properties 200 | */ 201 | 202 | Kifu.infoList = ["black", "white", "AN", "CP", "DT", "EV", "GN", "GC", "ON", "OT", "RE", "RO", "RU", "SO", "TM", "PC", "KM"]; 203 | 204 | WGo.Kifu = Kifu; 205 | 206 | var no_add = function(arr, obj, key) { 207 | for(var i = 0; i < arr.length; i++) { 208 | if(arr[i].x == obj.x && arr[i].y == obj.y) { 209 | arr[i][key] = obj[key]; 210 | return; 211 | } 212 | } 213 | arr.push(obj); 214 | } 215 | 216 | var no_remove = function(arr, obj) { 217 | if(!arr) remove; 218 | for(var i = 0; i < arr.length; i++) { 219 | if(arr[i].x == obj.x && arr[i].y == obj.y) { 220 | arr.splice(i,1); 221 | return; 222 | } 223 | } 224 | } 225 | 226 | /** 227 | * Node class of kifu game tree. It can contain move, setup or markup properties. 228 | * 229 | * @param {object} properties 230 | * @param {KNode} parent (null for root node) 231 | */ 232 | 233 | var KNode = function(properties, parent) { 234 | this.parent = parent || null; 235 | this.children = []; 236 | // save all properties 237 | if(properties) for(var key in properties) this[key] = properties[key]; 238 | } 239 | 240 | KNode.prototype = { 241 | constructor: KNode, 242 | 243 | /** 244 | * Get node's children specified by index. If it doesn't exist, method returns null. 245 | */ 246 | 247 | getChild: function(ch) { 248 | var i = ch || 0; 249 | if(this.children[i]) return this.children[i]; 250 | else return null; 251 | }, 252 | 253 | /** 254 | * Add setup property. 255 | * 256 | * @param {object} setup object with structure: {x:, y:, c:} 257 | */ 258 | 259 | addSetup: function(setup) { 260 | this.setup = this.setup || []; 261 | no_add(this.setup, setup, "c"); 262 | return this; 263 | }, 264 | 265 | /** 266 | * Remove setup property. 267 | * 268 | * @param {object} setup object with structure: {x:, y:} 269 | */ 270 | 271 | removeSetup: function(setup) { 272 | no_remove(this.setup, setup); 273 | return this; 274 | }, 275 | 276 | /** 277 | * Add markup property. 278 | * 279 | * @param {object} markup object with structure: {x:, y:, type:} 280 | */ 281 | 282 | addMarkup: function(markup) { 283 | this.markup = this.markup || []; 284 | no_add(this.markup, markup, "type"); 285 | return this; 286 | }, 287 | 288 | /** 289 | * Remove markup property. 290 | * 291 | * @param {object} markup object with structure: {x:, y:} 292 | */ 293 | 294 | removeMarkup: function(markup) { 295 | no_remove(this.markup, markup); 296 | return this; 297 | }, 298 | 299 | /** 300 | * Remove this node. 301 | * Node is removed from its parent and children are passed to parent. 302 | */ 303 | 304 | remove: function() { 305 | var p = this.parent; 306 | if(!p) throw new Exception("Root node cannot be removed"); 307 | for(var i in p.children) { 308 | if(p.children[i] == this) { 309 | p.children.splice(i,1); 310 | break; 311 | } 312 | } 313 | p.children = p.children.concat(this.children); 314 | this.parent = null; 315 | return p; 316 | }, 317 | 318 | /** 319 | * Insert node after this node. All children are passed to new node. 320 | */ 321 | 322 | insertAfter: function(node) { 323 | node.children = node.children.concat(this.children); 324 | node.parent = this; 325 | this.children = [node]; 326 | return node; 327 | }, 328 | 329 | /** 330 | * Append child node to this node. 331 | */ 332 | 333 | appendChild: function(node) { 334 | node.parent = this; 335 | this.children.push(node); 336 | return node; 337 | }, 338 | 339 | /** 340 | * Get properties as object. 341 | */ 342 | 343 | getProperties: function() { 344 | var props = {}; 345 | for(var key in this) { 346 | if(this.hasOwnProperty(key) && key != "children" && key != "parent") props[key] = this[key]; 347 | } 348 | return props; 349 | } 350 | } 351 | 352 | WGo.KNode = KNode; 353 | 354 | var pos_diff = function(old_p, new_p) { 355 | var size = old_p.size, add = [], remove = []; 356 | 357 | for(var i = 0; i < size*size; i++) { 358 | if(old_p.schema[i] && !new_p.schema[i]) remove.push({x:Math.floor(i/size),y:i%size}); 359 | else if(old_p.schema[i] != new_p.schema[i]) add.push({x:Math.floor(i/size),y:i%size,c:new_p.schema[i]}); 360 | } 361 | 362 | return { 363 | add: add, 364 | remove: remove 365 | } 366 | } 367 | 368 | /** 369 | * KifuReader object is capable of reading a kifu nodes and executing them. It contains Game object with actual position. 370 | * Variable change contains last changes of position. 371 | * If parameter rememberPath is set, KifuReader will remember last selected child of all nodes. 372 | */ 373 | 374 | var KifuReader = function(kifu, rememberPath) { 375 | this.kifu = kifu; 376 | this.node = this.kifu.root; 377 | this.game = new WGo.Game(this.kifu.size); 378 | this.path = {m:0}; 379 | 380 | this.change = exec_node(this.game, this.node, true); 381 | if(this.kifu.info["HA"] && this.kifu.info["HA"] > 1) this.game.turn = WGo.W; 382 | 383 | if(rememberPath) this.rememberPath = true; 384 | else this.rememberPath = false; 385 | } 386 | 387 | var set_subtract = function(a, b) { 388 | var n = [], q; 389 | for(var i in a) { 390 | q = true; 391 | for(var j in b) { 392 | if(a[i].x == b[j].x && a[i].y == b[j].y) { 393 | q = false; 394 | break; 395 | } 396 | } 397 | if(q) n.push(a[i]); 398 | } 399 | return n; 400 | } 401 | 402 | var concat_changes = function(ch_orig, ch_new) { 403 | ch_orig.add = set_subtract(ch_orig.add, ch_new.remove).concat(ch_new.add); 404 | ch_orig.remove = set_subtract(ch_orig.remove, ch_new.add).concat(ch_new.remove); 405 | } 406 | 407 | // change game object according to node, return changes 408 | var exec_node = function(game, node, first) { 409 | if(node.parent) node.parent._last_selected = node.parent.children.indexOf(node); 410 | 411 | if(node.move != undefined) { 412 | if(node.move.pass) { 413 | game.pass(node.move.c); 414 | return {add:[], remove:[]}; 415 | } 416 | else { 417 | var res = game.play(node.move.x, node.move.y, node.move.c); 418 | if(typeof res == "number") throw new InvalidMoveError(res, node); 419 | return { 420 | add: [node.move], 421 | remove: res 422 | } 423 | } 424 | } 425 | else if(node.setup != undefined) { 426 | if(!first) game.pushPosition(); 427 | 428 | var add = [], remove = []; 429 | 430 | for(var i in node.setup) { 431 | if(node.setup[i].c) { 432 | game.addStone(node.setup[i].x, node.setup[i].y, node.setup[i].c); 433 | add.push(node.setup[i]); 434 | } 435 | else { 436 | game.removeStone(node.setup[i].x, node.setup[i].y); 437 | remove.push(node.setup[i]); 438 | } 439 | } 440 | 441 | // TODO: check & test handling of turns 442 | if(node.turn) game.turn = node.turn; 443 | 444 | return { 445 | add: add, 446 | remove: remove 447 | }; 448 | } 449 | else if(!first) { 450 | game.pushPosition(); 451 | } 452 | return {add:[], remove:[]}; 453 | } 454 | 455 | var exec_next = function(i) { 456 | if(i === undefined && this.rememberPath) i = this.node._last_selected; 457 | i = i || 0; 458 | var node = this.node.children[i]; 459 | 460 | if(!node) return false; 461 | 462 | var ch = exec_node(this.game, node); 463 | 464 | this.path.m++; 465 | if(this.node.children.length > 1) this.path[this.path.m] = i; 466 | 467 | this.node = node; 468 | return ch; 469 | } 470 | 471 | var exec_previous = function() { 472 | if(!this.node.parent) return false; 473 | 474 | this.node = this.node.parent; 475 | 476 | this.game.popPosition(); 477 | 478 | this.path.m--; 479 | if(this.path[this.path.m] !== undefined) delete this.path[this.path.m]; 480 | 481 | return true; 482 | } 483 | 484 | var exec_first = function() { 485 | //if(!this.node.parent) return; 486 | 487 | this.game.firstPosition(); 488 | this.node = this.kifu.root; 489 | 490 | this.path = {m: 0}; 491 | 492 | this.change = exec_node(this.game, this.node, true); 493 | if(this.kifu.info["HA"] && this.kifu.info["HA"] > 1) this.game.turn = WGo.W; 494 | } 495 | 496 | KifuReader.prototype = { 497 | constructor: KifuReader, 498 | 499 | /** 500 | * Go to next node and if there is a move play it. 501 | */ 502 | 503 | next: function(i) { 504 | this.change = exec_next.call(this, i); 505 | return this; 506 | }, 507 | 508 | /** 509 | * Execute all nodes till the end. 510 | */ 511 | 512 | last: function() { 513 | var ch; 514 | this.change = { 515 | add: [], 516 | remove: [] 517 | } 518 | while(ch = exec_next.call(this)) concat_changes(this.change, ch); 519 | return this; 520 | }, 521 | 522 | /** 523 | * Return to the previous position (redo actual node) 524 | */ 525 | 526 | previous: function() { 527 | var old_pos = this.game.getPosition(); 528 | exec_previous.call(this); 529 | this.change = pos_diff(old_pos, this.game.getPosition()); 530 | return this; 531 | }, 532 | 533 | /** 534 | * Go to the initial position 535 | */ 536 | 537 | first: function() { 538 | var old_pos = this.game.getPosition(); 539 | exec_first.call(this); 540 | this.change = pos_diff(old_pos, this.game.getPosition()); 541 | return this; 542 | }, 543 | 544 | /** 545 | * Go to position specified by path object 546 | */ 547 | 548 | goTo: function(path) { 549 | if(path === undefined) return this; 550 | 551 | var old_pos = this.game.getPosition(); 552 | 553 | exec_first.call(this); 554 | 555 | var r; 556 | 557 | for(var i = 0; i < path.m; i++) { 558 | if(!exec_next.call(this, path[i+1])) { 559 | break; 560 | } 561 | } 562 | 563 | this.change = pos_diff(old_pos, this.game.getPosition()); 564 | return this; 565 | }, 566 | 567 | /** 568 | * Go to previous fork (a node with more than one child) 569 | */ 570 | 571 | previousFork: function() { 572 | var old_pos = this.game.getPosition(); 573 | while(exec_previous.call(this) && this.node.children.length == 1); 574 | this.change = pos_diff(old_pos, this.game.getPosition()); 575 | return this; 576 | }, 577 | 578 | /** 579 | * Shortcut. Get actual position object. 580 | */ 581 | 582 | getPosition: function() { 583 | return this.game.getPosition(); 584 | } 585 | } 586 | 587 | WGo.KifuReader = KifuReader; 588 | 589 | // Class handling invalid moves in kifu 590 | var InvalidMoveError = function(code, node) { 591 | this.name = "InvalidMoveError"; 592 | this.message = "Invalid move in kifu detected. "; 593 | 594 | if(node.move && node.move.c !== undefined && node.move.x !== undefined && node.move.y !== undefined) this.message += "Trying to play "+(node.move.c == WGo.WHITE ? "white" : "black")+" move on "+String.fromCharCode(node.move.x+65)+""+(19-node.move.y); 595 | else this.message += "Move object doesn't contain arbitrary attributes."; 596 | 597 | if(code) { 598 | switch(code) { 599 | case 1: 600 | this.message += ", but these coordinates are not on board."; 601 | break; 602 | case 2: 603 | this.message += ", but there already is a stone."; 604 | break; 605 | case 3: 606 | this.message += ", but this move is a suicide."; 607 | case 4: 608 | this.message += ", but this position already occured."; 609 | break; 610 | } 611 | } 612 | else this.message += "." 613 | } 614 | InvalidMoveError.prototype = new Error(); 615 | InvalidMoveError.prototype.constructor = InvalidMoveError; 616 | 617 | WGo.InvalidMoveError = InvalidMoveError; 618 | 619 | WGo.i18n.en["show"] = "show"; 620 | WGo.i18n.en["res-show-tip"] = "Click to show result."; 621 | 622 | })(WGo); 623 | -------------------------------------------------------------------------------- /interface/server/wgo/player.editable.js: -------------------------------------------------------------------------------- 1 | 2 | (function(WGo) { 3 | 4 | // board mousemove callback for edit move - adds highlighting 5 | var edit_board_mouse_move = function(x,y) { 6 | if(this.player.frozen || (this._lastX == x && this._lastY == y)) return; 7 | 8 | this._lastX = x; 9 | this._lastY = y; 10 | 11 | if(this._last_mark) { 12 | this.board.removeObject(this._last_mark); 13 | } 14 | 15 | if(x != -1 && y != -1 && this.player.kifuReader.game.isValid(x,y)) { 16 | this._last_mark = { 17 | type: "outline", 18 | x: x, 19 | y: y, 20 | c: this.player.kifuReader.game.turn 21 | }; 22 | this.board.addObject(this._last_mark); 23 | } 24 | else { 25 | delete this._last_mark; 26 | } 27 | } 28 | 29 | // board mouseout callback for edit move 30 | var edit_board_mouse_out = function() { 31 | if(this._last_mark) { 32 | this.board.removeObject(this._last_mark); 33 | delete this._last_mark; 34 | delete this._lastX; 35 | delete this._lastY; 36 | } 37 | } 38 | 39 | // get differences of two positions as a change object (TODO create a better solution, without need of this function) 40 | var pos_diff = function(old_p, new_p) { 41 | var size = old_p.size, add = [], remove = []; 42 | 43 | for(var i = 0; i < size*size; i++) { 44 | if(old_p.schema[i] && !new_p.schema[i]) remove.push({x:Math.floor(i/size),y:i%size}); 45 | else if(old_p.schema[i] != new_p.schema[i]) add.push({x:Math.floor(i/size),y:i%size,c:new_p.schema[i]}); 46 | } 47 | 48 | return { 49 | add: add, 50 | remove: remove 51 | } 52 | } 53 | 54 | WGo.Player.Editable = {}; 55 | 56 | /** 57 | * Toggle edit mode. 58 | */ 59 | 60 | WGo.Player.Editable = function(player, board) { 61 | this.player = player; 62 | this.board = board; 63 | this.editMode = false; 64 | } 65 | 66 | WGo.Player.Editable.prototype.set = function(set) { 67 | if(!this.editMode && set) { 68 | // save original kifu reader 69 | this.originalReader = this.player.kifuReader; 70 | 71 | // create new reader with cloned kifu 72 | this.player.kifuReader = new WGo.KifuReader(this.player.kifu.clone(), this.originalReader.rememberPath); 73 | 74 | // go to current position 75 | this.player.kifuReader.goTo(this.originalReader.path); 76 | 77 | // register edit listeners 78 | this._ev_click = this._ev_click || this.play.bind(this); 79 | this._ev_move = this._ev_move || edit_board_mouse_move.bind(this); 80 | this._ev_out = this._ev_out || edit_board_mouse_out.bind(this); 81 | 82 | this.board.addEventListener("click", this._ev_click); 83 | this.board.addEventListener("mousemove", this._ev_move); 84 | this.board.addEventListener("mouseout", this._ev_out); 85 | 86 | this.editMode = true; 87 | } 88 | else if(this.editMode && !set) { 89 | // go to the last original position 90 | this.originalReader.goTo(this.player.kifuReader.path); 91 | 92 | // change object isn't actual - update it, not elegant solution, but simple 93 | this.originalReader.change = pos_diff(this.player.kifuReader.getPosition(), this.originalReader.getPosition()); 94 | 95 | // update kifu reader 96 | this.player.kifuReader = this.originalReader; 97 | this.player.update(true); 98 | 99 | // remove edit listeners 100 | this.board.removeEventListener("click", this._ev_click); 101 | this.board.removeEventListener("mousemove", this._ev_move); 102 | this.board.removeEventListener("mouseout", this._ev_out); 103 | 104 | this.editMode = false; 105 | } 106 | } 107 | 108 | WGo.Player.Editable.prototype.play = function(x,y) { 109 | if(this.player.frozen || !this.player.kifuReader.game.isValid(x, y)) return; 110 | 111 | this.player.kifuReader.node.appendChild(new WGo.KNode({ 112 | move: { 113 | x: x, 114 | y: y, 115 | c: this.player.kifuReader.game.turn 116 | }, 117 | edited: true 118 | })); 119 | this.player.next(this.player.kifuReader.node.children.length-1); 120 | } 121 | 122 | if(WGo.BasicPlayer && WGo.BasicPlayer.component.Control) { 123 | WGo.BasicPlayer.component.Control.menu.push({ 124 | constructor: WGo.BasicPlayer.control.MenuItem, 125 | args: { 126 | name: "editmode", 127 | togglable: true, 128 | click: function(player) { 129 | this._editable = this._editable || new WGo.Player.Editable(player, player.board); 130 | this._editable.set(!this._editable.editMode); 131 | return this._editable.editMode; 132 | }, 133 | init: function(player) { 134 | var _this = this; 135 | player.addEventListener("frozen", function(e) { 136 | _this._disabled = _this.disabled; 137 | if(!_this.disabled) _this.disable(); 138 | }); 139 | player.addEventListener("unfrozen", function(e) { 140 | if(!_this._disabled) _this.enable(); 141 | delete _this._disabled; 142 | }); 143 | }, 144 | } 145 | }); 146 | } 147 | 148 | WGo.i18n.en["editmode"] = "Edit mode"; 149 | 150 | })(WGo); 151 | -------------------------------------------------------------------------------- /interface/server/wgo/player.js: -------------------------------------------------------------------------------- 1 | 2 | (function(WGo){ 3 | 4 | "use strict"; 5 | 6 | var FileError = function(path, code) { 7 | this.name = "FileError"; 8 | 9 | if(code == 1) this.message = "File '"+path+"' is empty."; 10 | else if(code == 2) this.message = "Network error. It is not possible to read '"+path+"'."; 11 | else this.message = "File '"+path+"' hasn't been found on server."; 12 | } 13 | 14 | FileError.prototype = new Error(); 15 | FileError.prototype.constructor = FileError; 16 | 17 | WGo.FileError = FileError; 18 | 19 | // ajax function for loading of files 20 | var loadFromUrl = WGo.loadFromUrl = function(url, callback) { 21 | 22 | var xmlhttp = new XMLHttpRequest(); 23 | xmlhttp.onreadystatechange = function() { 24 | if (xmlhttp.readyState == 4) { 25 | if(xmlhttp.status == 200) { 26 | if(xmlhttp.responseText.length == 0) { 27 | throw new FileError(url, 1); 28 | } 29 | else { 30 | callback(xmlhttp.responseText); 31 | } 32 | } 33 | else { 34 | throw new FileError(url); 35 | } 36 | } 37 | } 38 | 39 | try { 40 | xmlhttp.open("GET", url, true); 41 | xmlhttp.send(); 42 | } 43 | catch(err) { 44 | throw new FileError(url, 2); 45 | } 46 | 47 | } 48 | 49 | // basic updating function - handles board changes 50 | var update_board = function(e) { 51 | if(!e.change) return; 52 | 53 | // update board's position 54 | this.board.update(e.change); 55 | 56 | // remove old markers from the board 57 | if(this.temp_marks) this.board.removeObject(this.temp_marks); 58 | 59 | // init array for new objects 60 | var add = []; 61 | 62 | // add current move marker 63 | if(e.node.move) { 64 | if(e.node.move.pass) this.setMessage({ 65 | text: WGo.t((e.node.move.c == WGo.B ? "b" : "w")+"pass"), 66 | type: "notification" 67 | }); 68 | else add.push({ 69 | type: "CR", 70 | x: e.node.move.x, 71 | y: e.node.move.y 72 | }); 73 | } 74 | 75 | // add variantion letters 76 | if(e.node.children.length > 1) { 77 | for(var i = 0; i < e.node.children.length; i++) { 78 | if(e.node.children[i].move) add.push({ 79 | type: "LB", 80 | text: String.fromCharCode(65+i), 81 | x: e.node.children[i].move.x, 82 | y: e.node.children[i].move.y 83 | }); 84 | } 85 | } 86 | 87 | // add other markup 88 | if(e.node.markup) { 89 | for(var i in e.node.markup) { 90 | for(var j = 0; j < add.length; j++) { 91 | if(e.node.markup[i].x == add[j].x && e.node.markup[i].y == add[j].y) { 92 | add.splice(j,1); 93 | j--; 94 | } 95 | } 96 | } 97 | add = add.concat(e.node.markup); 98 | } 99 | 100 | // add new markers on the board 101 | this.temp_marks = add; 102 | this.board.addObject(add); 103 | } 104 | 105 | // preparing board 106 | var prepare_board = function(e) { 107 | // set board size 108 | this.board.setSize(e.kifu.size); 109 | 110 | // remove old objects 111 | this.board.removeAllObjects(); 112 | 113 | // activate wheel 114 | if(this.config.enableWheel) this.setWheel(true); 115 | } 116 | 117 | // detecting scrolling of element - e.g. when we are scrolling text in comment box, we want to be aware. 118 | var detect_scrolling = function(node, bp) { 119 | if(node == bp.element || node == bp.element) return false; 120 | else if(node._wgo_scrollable || (node.scrollHeight > node.offsetHeight)) return true; 121 | else return detect_scrolling(node.parentNode, bp); 122 | } 123 | 124 | // mouse wheel event callback, for replaying a game 125 | var wheel_lis = function(e) { 126 | var delta = e.wheelDelta || e.detail*(-1); 127 | 128 | // if there is scrolling in progress within an element, don't change position 129 | if(detect_scrolling(e.target, this)) return true; 130 | 131 | if(delta < 0) { 132 | this.next(); 133 | if(this.config.lockScroll && e.preventDefault) e.preventDefault(); 134 | return !this.config.lockScroll; 135 | } 136 | else if(delta > 0) { 137 | this.previous(); 138 | if(this.config.lockScroll && e.preventDefault) e.preventDefault(); 139 | return !this.config.lockScroll; 140 | } 141 | return true; 142 | }; 143 | 144 | // keyboard click callback, for replaying a game 145 | var key_lis = function(e) { 146 | switch(e.keyCode) { 147 | case 39: this.next(); break; 148 | case 37: this.previous(); break; 149 | //case 40: this.selectAlternativeVariation(); break; 150 | default: return true; 151 | } 152 | if(this.config.lockScroll && e.preventDefault) e.preventDefault() 153 | return !this.config.lockScroll; 154 | }; 155 | 156 | // function handling board clicks in normal mode 157 | var board_click_default = function(x,y) { 158 | if(!this.kifuReader || !this.kifuReader.node) return false; 159 | for(var i in this.kifuReader.node.children) { 160 | if(this.kifuReader.node.children[i].move && this.kifuReader.node.children[i].move.x == x && this.kifuReader.node.children[i].move.y == y) { 161 | this.next(i); 162 | return; 163 | } 164 | } 165 | } 166 | 167 | // coordinates drawing handler - adds coordinates on the board 168 | var coordinates = { 169 | grid: { 170 | draw: function(args, board) { 171 | var ch, t, xright, xleft, ytop, ybottom; 172 | 173 | this.fillStyle = "rgba(0,0,0,0.7)"; 174 | this.textBaseline="middle"; 175 | this.textAlign="center"; 176 | this.font = board.stoneRadius+"px "+(board.font || ""); 177 | 178 | xright = board.getX(-0.75); 179 | xleft = board.getX(board.size-0.25); 180 | ytop = board.getY(-0.75); 181 | ybottom = board.getY(board.size-0.25); 182 | 183 | for(var i = 0; i < board.size; i++) { 184 | ch = i+"A".charCodeAt(0); 185 | if(ch >= "I".charCodeAt(0)) ch++; 186 | 187 | t = board.getY(i); 188 | this.fillText(board.size-i, xright, t); 189 | this.fillText(board.size-i, xleft, t); 190 | 191 | t = board.getX(i); 192 | this.fillText(String.fromCharCode(ch), t, ytop); 193 | this.fillText(String.fromCharCode(ch), t, ybottom); 194 | } 195 | 196 | this.fillStyle = "black"; 197 | } 198 | } 199 | } 200 | 201 | /** 202 | * We can say this class is abstract, stand alone it doesn't do anything. 203 | * However it is useful skelet for building actual player's GUI. Extend this class to create custom player template. 204 | * It controls board and inputs from mouse and keyboard, but everything can be overriden. 205 | * 206 | * Possible configurations: 207 | * - sgf: sgf string (default: undefined) 208 | * - json: kifu stored in json/jgo (default: undefined) 209 | * - sgfFile: sgf file path (default: undefined) 210 | * - board: configuration object of board (default: {}) 211 | * - enableWheel: allow player to be controlled by mouse wheel (default: true) 212 | * - lockScroll: disable window scrolling while hovering player (default: true), 213 | * - enableKeys: allow player to be controlled by arrow keys (default: true), 214 | * 215 | * @param {object} config object if form: {key1: value1, key2: value2, ...} 216 | */ 217 | 218 | var Player = function(config) { 219 | this.config = config; 220 | 221 | // add default configuration 222 | for(var key in PlayerView.default) if(this.config[key] === undefined && PlayerView.default[key] !== undefined) this.config[key] = PlayerView.default[key]; 223 | 224 | this.element = document.createElement("div"); 225 | this.board = new WGo.Board(this.element, this.config.board); 226 | 227 | this.init(); 228 | this.initGame(); 229 | } 230 | 231 | Player.prototype = { 232 | constructor: Player, 233 | 234 | /** 235 | * Init player. If you want to call this method PlayerView object must have these properties: 236 | * - player - WGo.Player object 237 | * - board - WGo.Board object (or other board renderer) 238 | * - element - main DOMElement of player 239 | */ 240 | 241 | init: function() { 242 | // declare kifu 243 | this.kifu = null; 244 | 245 | // creating listeners 246 | this.listeners = { 247 | kifuLoaded: [prepare_board.bind(this)], 248 | update: [update_board.bind(this)], 249 | frozen: [], 250 | unfrozen: [], 251 | }; 252 | 253 | if(this.config.kifuLoaded) this.addEventListener("kifuLoaded", this.config.kifuLoaded); 254 | if(this.config.update) this.addEventListener("update", this.config.update); 255 | if(this.config.frozen) this.addEventListener("frozen", this.config.frozen); 256 | if(this.config.unfrozen) this.addEventListener("unfrozen", this.config.unfrozen); 257 | 258 | this.board.addEventListener("click", board_click_default.bind(this)); 259 | this.element.addEventListener("click", this.focus.bind(this)); 260 | 261 | this.focus(); 262 | }, 263 | 264 | initGame: function() { 265 | // try to load game passed in configuration 266 | if(this.config.sgf) { 267 | this.loadSgf(this.config.sgf, this.config.move); 268 | } 269 | else if(this.config.json) { 270 | this.loadJSON(this.config.json, this.config.move); 271 | } 272 | else if(this.config.sgfFile) { 273 | this.loadSgfFromFile(this.config.sgfFile, this.config.move); 274 | } 275 | 276 | }, 277 | 278 | /** 279 | * Create update event and dispatch it. It is called after position's changed. 280 | * 281 | * @param {string} op an operation that produced update (e.g. next, previous...) 282 | */ 283 | 284 | update: function(op) { 285 | if(!this.kifuReader || !this.kifuReader.change) return; 286 | 287 | var ev = { 288 | type: "update", 289 | op: op, 290 | target: this, 291 | node: this.kifuReader.node, 292 | position: this.kifuReader.getPosition(), 293 | path: this.kifuReader.path, 294 | change: this.kifuReader.change, 295 | } 296 | 297 | //if(!this.kifuReader.node.parent) ev.msg = this.getGameInfo(); 298 | 299 | this.dispatchEvent(ev); 300 | }, 301 | 302 | /** 303 | * Prepare kifu for replaying. Event 'kifuLoaded' is triggered. 304 | * 305 | * @param {WGo.Kifu} kifu object 306 | * @param {Array} path array 307 | */ 308 | 309 | loadKifu: function(kifu, path) { 310 | this.kifu = kifu; 311 | 312 | // kifu is replayed by KifuReader, it manipulates a Kifu object and gets all changes 313 | this.kifuReader = new WGo.KifuReader(this.kifu, this.config.rememberPath); 314 | 315 | // fire kifu loaded event 316 | this.dispatchEvent({ 317 | type: "kifuLoaded", 318 | target: this, 319 | kifu: this.kifu, 320 | }); 321 | 322 | // handle permalink 323 | /*if(this.config.permalinks) { 324 | if(!permalinks.active) init_permalinks(); 325 | if(permalinks.query.length && permalinks.query[0] == this.view.element.id) { 326 | handle_hash(this); 327 | } 328 | }*/ 329 | 330 | if(path) { 331 | this.goTo(path); 332 | } 333 | else { 334 | // update player - initial position in kifu doesn't have to be an empty board 335 | this.update("init"); 336 | } 337 | 338 | /*if(this.kifu.nodeCount === 0) this.error(""); 339 | else if(this.kifu.propertyCount === 0)*/ 340 | 341 | }, 342 | 343 | /** 344 | * Load go kifu from sgf string. 345 | * 346 | * @param {string} sgf 347 | */ 348 | 349 | loadSgf: function(sgf, path) { 350 | try { 351 | this.loadKifu(WGo.Kifu.fromSgf(sgf), path); 352 | } 353 | catch(err) { 354 | this.error(err); 355 | } 356 | }, 357 | 358 | /** 359 | * Load go kifu from JSON object. 360 | */ 361 | 362 | loadJSON: function(json) { 363 | try { 364 | this.loadKifu(WGo.Kifu.fromJGO(json), path); 365 | } 366 | catch(err) { 367 | this.error(err); 368 | } 369 | }, 370 | 371 | /** 372 | * Load kifu from sgf file specified with path. AJAX is used to load sgf content. 373 | */ 374 | 375 | loadSgfFromFile: function(file_path, game_path) { 376 | var _this = this; 377 | try { 378 | loadFromUrl(file_path, function(sgf) { 379 | _this.loadSgf(sgf, game_path); 380 | }); 381 | } 382 | catch(err) { 383 | this.error(err); 384 | } 385 | }, 386 | 387 | /** 388 | * Implementation of EventTarget interface, though it's a little bit simplified. 389 | * You need to save listener if you would like to remove it later. 390 | * 391 | * @param {string} type of listeners 392 | * @param {Function} listener callback function 393 | */ 394 | 395 | addEventListener: function(type, listener) { 396 | this.listeners[type] = this.listeners[type] || []; 397 | this.listeners[type].push(listener); 398 | }, 399 | 400 | /** 401 | * Remove event listener previously added with addEventListener. 402 | * 403 | * @param {string} type of listeners 404 | * @param {Function} listener function 405 | */ 406 | 407 | removeEventListener: function(type, listener) { 408 | if(!this.listeners[type]) return; 409 | var i = this.listeners[type].indexOf(listener); 410 | if(i != -1) this.listeners[type].splice(i,1); 411 | }, 412 | 413 | /** 414 | * Dispatch an event. In default there are two events: "kifuLoaded" and "update" 415 | * 416 | * @param {string} evt event 417 | */ 418 | 419 | dispatchEvent: function(evt) { 420 | if(!this.listeners[evt.type]) return; 421 | for(var l in this.listeners[evt.type]) this.listeners[evt.type][l](evt); 422 | }, 423 | 424 | /** 425 | * Output function for notifications. 426 | */ 427 | 428 | notification: function(text) { 429 | if(console) console.log(text); 430 | }, 431 | 432 | /** 433 | * Output function for helps. 434 | */ 435 | 436 | help: function(text) { 437 | if(console) console.log(text); 438 | }, 439 | 440 | /** 441 | * Output function for errors. TODO: reporting of errors - by cross domain AJAX 442 | */ 443 | 444 | error: function(err) { 445 | if(!WGo.ERROR_REPORT) throw err; 446 | 447 | if(console) console.log(err); 448 | 449 | }, 450 | 451 | /** 452 | * Play next move. 453 | * 454 | * @param {number} i if there is more option, you can specify it by index 455 | */ 456 | 457 | next: function(i) { 458 | if(this.frozen || !this.kifu) return; 459 | 460 | try { 461 | this.kifuReader.next(i); 462 | this.update(); 463 | } 464 | catch(err) { 465 | this.error(err); 466 | } 467 | }, 468 | 469 | /** 470 | * Get previous position. 471 | */ 472 | 473 | previous: function() { 474 | if(this.frozen || !this.kifu) return; 475 | 476 | try{ 477 | this.kifuReader.previous(); 478 | this.update(); 479 | } 480 | catch(err) { 481 | this.error(err); 482 | } 483 | }, 484 | 485 | /** 486 | * Play all moves and get last position. 487 | */ 488 | 489 | last: function() { 490 | if(this.frozen || !this.kifu) return; 491 | 492 | try { 493 | this.kifuReader.last(); 494 | this.update(); 495 | } 496 | catch(err) { 497 | this.error(err); 498 | } 499 | }, 500 | 501 | /** 502 | * Get a first position. 503 | */ 504 | 505 | first: function() { 506 | if(this.frozen || !this.kifu) return; 507 | 508 | try { 509 | this.kifuReader.first(); 510 | this.update(); 511 | } 512 | catch(err) { 513 | this.error(err); 514 | } 515 | }, 516 | 517 | /** 518 | * Go to a specified move. 519 | * 520 | * @param {number|Array} move number of move, or path array 521 | */ 522 | 523 | goTo: function(move) { 524 | if(this.frozen || !this.kifu) return; 525 | var path; 526 | if(typeof move == "function") move = move.call(this); 527 | 528 | if(typeof move == "number") { 529 | path = WGo.clone(this.kifuReader.path); 530 | path.m = move || 0; 531 | } 532 | else path = move; 533 | 534 | try { 535 | this.kifuReader.goTo(path); 536 | this.update(); 537 | } 538 | catch(err) { 539 | this.error(err); 540 | } 541 | }, 542 | 543 | /** 544 | * Get information about actual game(kifu) 545 | * 546 | * @return {Object} game info 547 | */ 548 | 549 | getGameInfo: function() { 550 | if(!this.kifu) return null; 551 | var info = {}; 552 | for(var key in this.kifu.info) { 553 | if(WGo.Kifu.infoList.indexOf(key) == -1) continue; 554 | if(WGo.Kifu.infoFormatters[key]) { 555 | info[WGo.t(key)] = WGo.Kifu.infoFormatters[key](this.kifu.info[key]); 556 | } 557 | else info[WGo.t(key)] = WGo.filterHTML(this.kifu.info[key]); 558 | } 559 | return info; 560 | }, 561 | 562 | /** 563 | * Freeze or onfreeze player. In frozen state methods: next, previous etc. don't work. 564 | */ 565 | 566 | setFrozen: function(frozen) { 567 | this.frozen = frozen; 568 | this.dispatchEvent({ 569 | type: this.frozen ? "frozen" : "unfrozen", 570 | target: this, 571 | }); 572 | }, 573 | 574 | /** 575 | * Append player to given element. 576 | */ 577 | 578 | appendTo: function(elem) { 579 | elem.appendChild(this.element); 580 | }, 581 | 582 | /** 583 | * Get focus on the player 584 | */ 585 | 586 | focus: function() { 587 | if(this.config.enableKeys) this.setKeys(true); 588 | }, 589 | 590 | /** 591 | * Set controlling of player by arrow keys. 592 | */ 593 | 594 | setKeys: function(b) { 595 | if(b) { 596 | if(WGo.mozilla) document.onkeypress = key_lis.bind(this); 597 | else document.onkeydown = key_lis.bind(this); 598 | } 599 | else { 600 | if(WGo.mozilla) document.onkeypress = null; 601 | else document.onkeydown = null; 602 | } 603 | }, 604 | 605 | /** 606 | * Set controlling of player by mouse wheel. 607 | */ 608 | 609 | setWheel: function(b) { 610 | if(!this._wheel_listener && b) { 611 | this._wheel_listener = wheel_lis.bind(this); 612 | var type = WGo.mozilla ? "DOMMouseScroll" : "mousewheel"; 613 | this.element.addEventListener(type, this._wheel_listener); 614 | } 615 | else if(this._wheel_listener && !b) { 616 | var type = WGo.mozilla ? "DOMMouseScroll" : "mousewheel"; 617 | this.element.removeEventListener(type, this._wheel_listener); 618 | delete this._wheel_listener; 619 | } 620 | }, 621 | 622 | /** 623 | * Toggle coordinates around the board. 624 | */ 625 | 626 | setCoordinates: function(b) { 627 | if(!this.coordinates && b) { 628 | this.board.setSection(-0.5, -0.5, -0.5, -0.5); 629 | this.board.addCustomObject(coordinates); 630 | } 631 | else if(this.coordinates && !b) { 632 | this.board.setSection(0, 0, 0, 0); 633 | this.board.removeCustomObject(coordinates); 634 | } 635 | this.coordinates = b; 636 | }, 637 | 638 | } 639 | 640 | Player.default = { 641 | sgf: undefined, 642 | json: undefined, 643 | sgfFile: undefined, 644 | move: undefined, 645 | board: {}, 646 | enableWheel: true, 647 | lockScroll: true, 648 | enableKeys: true, 649 | rememberPath: true, 650 | kifuLoaded: undefined, 651 | update: undefined, 652 | frozen: undefined, 653 | unfrozen: undefined, 654 | } 655 | 656 | WGo.Player = Player; 657 | 658 | //--- i18n support ------------------------------------------------------------------------------------------ 659 | 660 | /** 661 | * For another language support, extend this object with similiar object. 662 | */ 663 | 664 | var player_terms = { 665 | "about-text": "

WGo.js Player 2.0

" 666 | + "

WGo.js Player is extension of WGo.js, HTML5 library for purposes of game of go. It allows to replay go game records and it has many features like score counting. It is also designed to be easily extendable.

" 667 | + "

WGo.js is open source licensed under MIT license. You can use and modify any code from this project.

" 668 | + "

You can find more information at wgo.waltheri.net/player

" 669 | + "

Copyright © 2013 Jan Prokop

", 670 | "black": "Black", 671 | "white": "White", 672 | "DT": "Date", 673 | "KM": "Komi", 674 | "HA": "Handicap", 675 | "AN": "Annotations", 676 | "CP": "Copyright", 677 | "OT": "Overtime", 678 | "TM": "Basic time", 679 | "RE": "Result", 680 | "RU": "Rules", 681 | "PC": "Place", 682 | "EV": "Event", 683 | "SO": "Source", 684 | "none": "none", 685 | "bpass": "Black passed.", 686 | "wpass": "White passed.", 687 | }; 688 | 689 | for(var key in player_terms) WGo.i18n.en[key] = player_terms[key]; 690 | 691 | })(WGo); 692 | -------------------------------------------------------------------------------- /interface/server/wgo/player.permalink.js: -------------------------------------------------------------------------------- 1 | (function(WGo, undefined) { 2 | 3 | "use strict"; 4 | 5 | var permalink = { 6 | active: true, 7 | query: {}, 8 | }; 9 | 10 | var handle_hash = function(player) { 11 | try { 12 | permalink.query = JSON.parse('{"'+window.location.hash.substr(1).replace('=', '":')+'}'); 13 | } 14 | catch(e) { 15 | permalink.query = {}; 16 | } 17 | } 18 | 19 | // add hashchange event 20 | window.addEventListener("hashchange", function() { 21 | if(window.location.hash != "" && permalink.active) { 22 | handle_hash(); 23 | 24 | for(var key in permalink.query) { 25 | var p_el = document.getElementById(key); 26 | if(p_el && p_el._wgo_player) p_el._wgo_player.goTo(move_from_hash); 27 | } 28 | } 29 | }); 30 | 31 | // save hash query (after DOM is loaded - you can turn this off by setting WGo.Player.permalink.active = false; 32 | window.addEventListener("DOMContentLoaded", function() { 33 | if(window.location.hash != "" && permalink.active) { 34 | handle_hash(); 35 | } 36 | }); 37 | 38 | // scroll into view of the board 39 | window.addEventListener("load", function() { 40 | if(window.location.hash != "" && permalink.active) { 41 | for(var key in permalink.query) { 42 | var p_el = document.getElementById(key); 43 | if(p_el && p_el._wgo_player) { 44 | p_el.scrollIntoView(); 45 | break; 46 | } 47 | } 48 | } 49 | }); 50 | 51 | var move_from_hash = function() { 52 | if(permalink.query[this.element.id]) { 53 | return permalink.query[this.element.id].goto; 54 | } 55 | } 56 | 57 | WGo.Player.default.move = move_from_hash; 58 | 59 | // add menu item 60 | if(WGo.BasicPlayer && WGo.BasicPlayer.component.Control) { 61 | WGo.BasicPlayer.component.Control.menu.push({ 62 | constructor: WGo.BasicPlayer.control.MenuItem, 63 | args: { 64 | name: "permalink", 65 | click: function(player) { 66 | var link = location.href.split("#")[0]+'#'+player.element.id+'={"goto":'+JSON.stringify(player.kifuReader.path)+'}'; 67 | player.showMessage('

'+WGo.t('permalink')+'

'); 68 | }, 69 | } 70 | }); 71 | } 72 | 73 | WGo.Player.permalink = permalink; 74 | WGo.i18n.en["permalink"] = "Permanent link"; 75 | 76 | })(WGo); -------------------------------------------------------------------------------- /interface/server/wgo/scoremode.js: -------------------------------------------------------------------------------- 1 | 2 | (function(WGo){ 3 | 4 | var ScoreMode = function(position, board, komi, output) { 5 | this.originalPosition = position; 6 | this.position = position.clone(); 7 | this.board = board; 8 | this.komi = komi; 9 | this.output = output; 10 | } 11 | 12 | var state = ScoreMode.state = { 13 | UNKNOWN: 0, 14 | BLACK_STONE: 1, // must be equal to WGo.B 15 | WHITE_STONE: -1, // must be equal to WGo.W 16 | BLACK_CANDIDATE: 2, 17 | WHITE_CANDIDATE: -2, 18 | BLACK_NEUTRAL: 3, 19 | WHITE_NEUTRAL: -3, 20 | NEUTRAL: 4, 21 | } 22 | 23 | var territory_set = function(pos, x, y, color, margin) { 24 | var p = pos.get(x, y); 25 | if(p === undefined || p == color || p == margin) return; 26 | 27 | pos.set(x, y, color); 28 | 29 | territory_set(pos, x-1, y, color, margin); 30 | territory_set(pos, x, y-1, color, margin); 31 | territory_set(pos, x+1, y, color, margin); 32 | territory_set(pos, x, y+1, color, margin); 33 | } 34 | 35 | var territory_reset = function(pos, orig, x, y, margin) { 36 | var o = orig.get(x, y); 37 | if(pos.get(x, y) == o) return; 38 | 39 | pos.set(x, y, o); 40 | territory_reset(pos, orig, x-1, y, margin); 41 | territory_reset(pos, orig, x, y-1, margin); 42 | territory_reset(pos, orig, x+1, y, margin); 43 | territory_reset(pos, orig, x, y+1, margin); 44 | } 45 | 46 | ScoreMode.prototype.start = function() { 47 | this.calculate(); 48 | this.saved_state = this.board.getState(); 49 | this.displayScore(); 50 | 51 | this._click = (function(x,y) { 52 | var c = this.originalPosition.get(x,y); 53 | 54 | if(c == WGo.W) { 55 | if(this.position.get(x, y) == state.WHITE_STONE) territory_set(this.position, x, y, state.BLACK_CANDIDATE, state.BLACK_STONE); 56 | else { 57 | territory_reset(this.position, this.originalPosition, x, y, state.BLACK_STONE); 58 | this.calculate(); 59 | } 60 | } 61 | else if(c == WGo.B) { 62 | if(this.position.get(x, y) == state.BLACK_STONE) territory_set(this.position, x, y, state.WHITE_CANDIDATE, state.WHITE_STONE); 63 | else { 64 | territory_reset(this.position, this.originalPosition, x, y, state.WHITE_STONE); 65 | this.calculate(); 66 | } 67 | } 68 | else { 69 | var p = this.position.get(x, y); 70 | 71 | if(p == state.BLACK_CANDIDATE) this.position.set(x, y, state.BLACK_NEUTRAL); 72 | else if(p == state.WHITE_CANDIDATE) this.position.set(x, y, state.WHITE_NEUTRAL); 73 | else if(p == state.BLACK_NEUTRAL) this.position.set(x, y, state.BLACK_CANDIDATE); 74 | else if(p == state.WHITE_NEUTRAL) this.position.set(x, y, state.WHITE_CANDIDATE); 75 | } 76 | 77 | this.board.restoreState({objects: WGo.clone(this.saved_state.objects)}); 78 | this.displayScore(); 79 | }).bind(this); 80 | 81 | this.board.addEventListener("click", this._click); 82 | } 83 | 84 | ScoreMode.prototype.end = function() { 85 | this.board.restoreState({objects: WGo.clone(this.saved_state.objects)}); 86 | this.board.removeEventListener("click", this._click); 87 | } 88 | 89 | ScoreMode.prototype.displayScore = function() { 90 | var score = { 91 | black: [], 92 | white: [], 93 | neutral: [], 94 | black_captured: [], 95 | white_captured: [], 96 | } 97 | 98 | for(var i = 0; i < this.position.size; i++) { 99 | for(var j = 0; j < this.position.size; j++) { 100 | s = this.position.get(i,j); 101 | t = this.originalPosition.get(i,j); 102 | 103 | if(s == state.BLACK_CANDIDATE) score.black.push({x: i, y: j, type: "mini", c: WGo.B}); 104 | else if(s == state.WHITE_CANDIDATE) score.white.push({x: i, y: j, type: "mini", c: WGo.W}); 105 | else if(s == state.NEUTRAL) score.neutral.push({x: i, y: j}); 106 | 107 | if(t == WGo.W && s != state.WHITE_STONE) score.white_captured.push({x: i, y: j, type: "outline", c: WGo.W}); 108 | else if(t == WGo.B && s != state.BLACK_STONE) score.black_captured.push({x: i, y: j, type: "outline", c: WGo.B}); 109 | } 110 | } 111 | 112 | for(var i = 0; i < score.black_captured.length; i++) { 113 | this.board.removeObjectsAt(score.black_captured[i].x, score.black_captured[i].y); 114 | } 115 | 116 | for(var i = 0; i < score.white_captured.length; i++) { 117 | this.board.removeObjectsAt(score.white_captured[i].x, score.white_captured[i].y); 118 | } 119 | 120 | this.board.addObject(score.white_captured); 121 | this.board.addObject(score.black_captured); 122 | this.board.addObject(score.black); 123 | this.board.addObject(score.white); 124 | 125 | var msg = "

"+WGo.t("RE")+"

"; 126 | 127 | var sb = score.black.length+score.white_captured.length+this.originalPosition.capCount.black; 128 | var sw = score.white.length+score.black_captured.length+this.originalPosition.capCount.white+parseFloat(this.komi); 129 | 130 | msg += "

"+WGo.t("black")+": "+score.black.length+" + "+(score.white_captured.length+this.originalPosition.capCount.black)+" = "+sb+"
"; 131 | msg += WGo.t("white")+": "+score.white.length+" + "+(score.black_captured.length+this.originalPosition.capCount.white)+" + "+this.komi+" = "+sw+"

"; 132 | 133 | if(sb > sw) msg += "

"+WGo.t("bwin", sb-sw)+"

"; 134 | else msg += "

"+WGo.t("wwin", sw-sb)+"

"; 135 | 136 | this.output(msg); 137 | } 138 | 139 | ScoreMode.prototype.calculate = function() { 140 | var p, s, t, b, w, change; 141 | 142 | // 1. create testing position, empty fields has flag ScoreMode.UNKNOWN 143 | p = this.position; 144 | 145 | // 2. repeat until there is some change of state: 146 | change = true; 147 | while(change) { 148 | change = false; 149 | 150 | // go through the whole position 151 | for(var i = 0; i < p.size; i++) { 152 | //var str = ""; 153 | for(var j = 0; j < p.size; j++) { 154 | s = p.get(j,i); 155 | 156 | if(s == state.UNKNOWN || s == state.BLACK_CANDIDATE || s == state.WHITE_CANDIDATE) { 157 | // get new state 158 | t = [p.get(j-1, i), p.get(j, i-1), p.get(j+1, i), p.get(j, i+1)]; 159 | b = false; 160 | w = false; 161 | 162 | for(var k = 0; k < 4; k++) { 163 | if(t[k] == state.BLACK_STONE || t[k] == state.BLACK_CANDIDATE) b = true; 164 | else if(t[k] == state.WHITE_STONE || t[k] == state.WHITE_CANDIDATE) w = true; 165 | else if(t[k] == state.NEUTRAL) { 166 | b = true; 167 | w = true; 168 | } 169 | } 170 | 171 | t = false; 172 | 173 | if(b && w) t = state.NEUTRAL; 174 | else if(b) t = state.BLACK_CANDIDATE; 175 | else if(w) t = state.WHITE_CANDIDATE; 176 | 177 | if(t && s != t) { 178 | change = true; 179 | p.set(j, i, t); 180 | } 181 | } 182 | //str += (p.get(j,i)+5)+" "; 183 | } 184 | //console.log(str); 185 | } 186 | //console.log("------------------------------------------------------------"); 187 | } 188 | } 189 | 190 | WGo.ScoreMode = ScoreMode; 191 | 192 | if(WGo.BasicPlayer && WGo.BasicPlayer.component.Control) { 193 | WGo.BasicPlayer.component.Control.menu.push({ 194 | constructor: WGo.BasicPlayer.control.MenuItem, 195 | args: { 196 | name: "scoremode", 197 | togglable: true, 198 | click: function(player) { 199 | if(this.selected) { 200 | player.setFrozen(false); 201 | this._score_mode.end(); 202 | delete this._score_mode; 203 | player.notification(); 204 | player.help(); 205 | return false; 206 | } 207 | else { 208 | player.setFrozen(true); 209 | player.help("

"+WGo.t("help_score")+"

"); 210 | this._score_mode = new WGo.ScoreMode(player.kifuReader.game.position, player.board, player.kifu.info.KM || 0.5, player.notification); 211 | this._score_mode.start(); 212 | return true; 213 | } 214 | }, 215 | } 216 | }); 217 | } 218 | 219 | WGo.i18n.en["scoremode"] = "Count score"; 220 | WGo.i18n.en["score"] = "Score"; 221 | WGo.i18n.en["bwin"] = "Black wins by $ points."; 222 | WGo.i18n.en["wwin"] = "White wins by $ points."; 223 | WGo.i18n.en["help_score"] = "Click on stones to mark them dead or alive. You can also set and unset territory points by clicking on them. Territories must be completely bordered."; 224 | 225 | })(WGo); 226 | -------------------------------------------------------------------------------- /interface/server/wgo/sgfparser.js: -------------------------------------------------------------------------------- 1 | 2 | (function(WGo, undefined){ 3 | 4 | WGo.SGF = {}; 5 | 6 | var to_num = function(str, i) { 7 | return str.charCodeAt(i)-97; 8 | } 9 | 10 | var sgf_player_info = function(type, black, kifu, node, value, ident) { 11 | var c = ident == black ? "black" : "white"; 12 | kifu.info[c] = kifu.info[c] || {}; 13 | kifu.info[c][type] = value[0]; 14 | } 15 | 16 | // handling properties specifically 17 | var properties = WGo.SGF.properties = {} 18 | 19 | // Move properties 20 | properties["B"] = properties["W"] = function(kifu, node, value, ident) { 21 | if(!value[0] || (kifu.size <= 19 && value[0] == "tt")) node.move = { 22 | pass: true, 23 | c: ident == "B" ? WGo.B : WGo.W 24 | }; 25 | else node.move = { 26 | x: to_num(value[0], 0), 27 | y: to_num(value[0], 1), 28 | c: ident == "B" ? WGo.B : WGo.W 29 | }; 30 | } 31 | 32 | // Setup properties 33 | properties["AB"] = properties["AW"] = function(kifu, node, value, ident) { 34 | for(var i in value) { 35 | node.addSetup({ 36 | x: to_num(value[i], 0), 37 | y: to_num(value[i], 1), 38 | c: ident == "AB" ? WGo.B : WGo.W 39 | }); 40 | } 41 | } 42 | properties["AE"] = function(kifu, node, value) { 43 | for(var i in value) { 44 | node.addSetup({ 45 | x: to_num(value[i], 0), 46 | y: to_num(value[i], 1), 47 | }); 48 | } 49 | } 50 | properties["PL"] = function(kifu, node, value) { 51 | node.turn = value[0] == "b" ? WGo.B : WGo.W; 52 | } 53 | 54 | // Node annotation properties 55 | properties["C"] = function(kifu, node, value) { 56 | node.comment = value.join(); 57 | } 58 | 59 | // Markup properties 60 | properties["LB"] = function(kifu, node, value) { 61 | for(var i in value) { 62 | node.addMarkup({ 63 | x: to_num(value[i],0), 64 | y: to_num(value[i],1), 65 | type: "LB", 66 | text: value[i].substr(3) 67 | }); 68 | } 69 | } 70 | properties["CR"] = properties["SQ"] = properties["TR"] = properties["SL"] = properties["MA"] = function(kifu, node, value, ident) { 71 | for(var i in value) { 72 | node.addMarkup({ 73 | x: to_num(value[i],0), 74 | y: to_num(value[i],1), 75 | type: ident 76 | }); 77 | } 78 | } 79 | 80 | // Root properties 81 | properties["SZ"] = function(kifu, node, value) { 82 | kifu.size = parseInt(value[0]); 83 | } 84 | 85 | // Game info properties 86 | properties["BR"] = properties["WR"] = sgf_player_info.bind(this, "rank", "BR"); 87 | properties["PB"] = properties["PW"] = sgf_player_info.bind(this, "name", "PB"); 88 | properties["BT"] = properties["WT"] = sgf_player_info.bind(this, "team", "BT"); 89 | properties["TM"] = function(kifu, node, value, ident) { 90 | kifu.info[ident] = value[0]; 91 | node.BL = value[0]; 92 | node.WL = value[0]; 93 | } 94 | 95 | var reg_seq = /\(|\)|(;(\s*[A-Z]+\s*((\[\])|(\[(.|\s)*?([^\\]\])))+)*)/g; 96 | var reg_node = /[A-Z]+\s*((\[\])|(\[(.|\s)*?([^\\]\])))+/g; 97 | var reg_ident = /[A-Z]+/; 98 | var reg_props = /(\[\])|(\[(.|\s)*?([^\\]\]))/g; 99 | 100 | // parse SGF string, return WGo.Kifu object 101 | WGo.SGF.parse = function(str) { 102 | 103 | var stack = [], 104 | sequence, props, vals, ident, 105 | kifu = new WGo.Kifu(), 106 | node = null; 107 | 108 | // make sequence of elements and process it 109 | sequence = str.match(reg_seq); 110 | 111 | for(var i in sequence) { 112 | // push stack, if new variant 113 | if(sequence[i] == "(") stack.push(node); 114 | 115 | // pop stack at the end of variant 116 | else if(sequence[i] == ")") node = stack.pop(); 117 | 118 | // reading node (string starting with ';') 119 | else { 120 | // create node or use root 121 | if(node) kifu.nodeCount++; 122 | node = node ? node.appendChild(new WGo.KNode()) : kifu.root; 123 | 124 | // make array of properties 125 | props = sequence[i].match(reg_node); 126 | kifu.propertyCount += props.length; 127 | 128 | // insert all properties to node 129 | for(var j in props) { 130 | // get property's identificator 131 | ident = reg_ident.exec(props[j])[0]; 132 | 133 | // separate property's values 134 | vals = props[j].match(reg_props); 135 | 136 | // remove additional braces [ and ] 137 | for(var k in vals) vals[k] = vals[k].substring(1, vals[k].length-1).replace(/\\(?!\\)/g, ""); 138 | 139 | // call property handler if any 140 | if(WGo.SGF.properties[ident]) WGo.SGF.properties[ident](kifu, node, vals, ident); 141 | else { 142 | // if there is only one property, strip array 143 | if(vals.length <= 1) vals = vals[0]; 144 | 145 | // default node property saving 146 | if(node.parent) node[ident] = vals; 147 | 148 | // default root property saving 149 | else { 150 | kifu.info[ident] = vals; 151 | } 152 | } 153 | } 154 | } 155 | } 156 | 157 | return kifu; 158 | } 159 | })(WGo); -------------------------------------------------------------------------------- /interface/server/wgo/wgo.min.js: -------------------------------------------------------------------------------- 1 | /*! MIT license, more info: wgo.waltheri.net */ (function(e,t){"use strict";var n=document.getElementsByTagName("script");var r=n[n.length-1].src.split("?")[0];var i=r.split("/").slice(0,-1).join("/")+"/";var s={version:"2.0",B:1,W:-1,ERROR_REPORT:true,DIR:i,lang:"en",i18n:{en:{}}};s.opera=navigator.userAgent.search(/(opera)(?:.*version)?[ \/]([\w.]+)/i)!=-1;s.webkit=navigator.userAgent.search(/(webkit)[ \/]([\w.]+)/i)!=-1;s.msie=navigator.userAgent.search(/(msie) ([\w.]+)/i)!=-1;s.mozilla=navigator.userAgent.search(/(mozilla)(?:.*? rv:([\w.]+))?/i)!=-1&&!s.webkit&&!s.msie;s.t=function(e){var t=s.i18n[s.lang][e]||s.i18n.en[e];if(t){for(var n=1;n",">")};var o=function(e,n){var n=n||{};for(var r in n)this[r]=n[r];for(var r in s.Board.default)if(this[r]===t)this[r]=s.Board.default[r];this.tx=this.section.left;this.ty=this.section.top;this.bx=this.size-1-this.section.right;this.by=this.size-1-this.section.bottom;this.init();e.appendChild(this.element);if(this.width&&this.height)this.setDimensions(this.width,this.height);else if(this.width)this.setWidth(this.width);else if(this.height)this.setHeight(this.height)};var u={draw:function(e,t){var n=t.getX(e.x),r=t.getY(e.y),i=t.stoneRadius;this.beginPath();this.fillStyle="rgba(32,32,32,0.5)";this.arc(n-.5,r-.5,i-.5,0,2*Math.PI,true);this.fill()}};var a=function(e,t,n){if(e.obj_arr[t][n][0].c==s.B)return"white";return"black"};var f=function(e,t){var n;e[t].clear();e[t].draw(e);for(var r=0;r=this.size?-1:i,y:s>=this.size?-1:s}};var y=function(){this.element.style.width=this.width+"px";this.element.style.height=this.height+"px";this.stoneRadius=this.stoneSize*Math.min(this.fieldWidth,this.fieldHeight)/2;for(var e in this.layers){this.layers[e].setDimensions(this.width,this.height)}};o.prototype={constructor:o,init:function(){this.obj_arr=[];for(var e=0;e=0){this.layers.splice(t,1);this.element.removeChild(e.element)}},update:function(e){if(e.remove&&e.remove=="all")this.removeAllObjects();else if(e.remove){for(var t in e.remove)this.removeObject(e.remove[t])}if(e.add){for(var t in e.add)this.addObject(e.add[t])}},addObject:function(e){if(e.constructor==Array){for(var t in e)this.addObject(e[t]);return}v.call(this,e.x,e.y);for(var t in this.obj_arr[e.x][e.y]){if(this.obj_arr[e.x][e.y][t].type==e.type){this.obj_arr[e.x][e.y][t]=e;m.call(this,e.x,e.y);return}}if(!e.type)this.obj_arr[e.x][e.y].unshift(e);else this.obj_arr[e.x][e.y].push(e);m.call(this,e.x,e.y)},removeObject:function(e){if(e.constructor==Array){for(var n in e)this.removeObject(e[n]);return}var r;for(var i=0;i=this.size||n>=this.size)return t;return this.schema[e*this.size+n]},set:function(e,t,n){this.schema[e*this.size+t]=n;return this},clear:function(){for(var e=0;e=0&&n=0&&r=e.size||r<0||r>=e.size)return true;if(e.get(n,r)==0)return false;if(t.get(n,r)==true||e.get(n,r)==-i)return true;t.set(n,r,true);return S(e,t,n,r-1,i)&&S(e,t,n,r+1,i)&&S(e,t,n-1,r,i)&&S(e,t,n+1,r,i)};var x=function(e,t,n,r){var i=[];if(t>=0&&t=0&&n=0)i=this.stack.length-2;else if(this.repeating=="ALL")i=0;else return true;for(var s=this.stack.length-2;s>=i;s--){if(this.stack[s].get(t,n)==e.get(t,n)){r=true;for(var o=0;o=0&&t>=0&&e0){e=this.stack.pop();if(this.stack.length==0)this.turn=s.B;else if(this.position.color)this.turn=-this.position.color;else this.turn=-this.turn}return e},firstPosition:function(){this.stack=[];this.stack[0]=new b(this.size);this.stack[0].capCount={black:0,white:0};this.turn=s.B;return this},getCaptureCount:function(e){return e==s.B?this.position.capCount.black:this.position.capCount.white},validatePosition:function(){var e,t,n=0,r=0,i=[],o=this.position.clone();for(var u=0;u