sss: chat control, enter/leave
Author: chessslut
Description Source Code Launch Bot Current Users

Short Description:

enter leave notify and grey control and chat control and some automod (wip)

Full Description

Combining all my favorite stuff.....but i'm not sure what i'm doin lmao
-
-
-
-
-
------
---------------
LOW VISITOR BOT (on)
----------------------------
https://www.freesexcam.ca/apps/sourcecode/l ow-visitor-bot/?version=&slot=1
------------------------------------
-----------------------------------
Notify the broadcaster when a grey visitor enters or leaves the room.

Notify when a big tipper enters/leaves the room.
----------------------------
SAUCE:
cb.onEnter(function(user) {
if(!user['has_tokens']) {
cb.sendNotice('A new user ' + user['user'] + ' (' + user['gender'] + ') just entered the room!', cb.room_slug, '#ffffff', '#47c93e', 'bold');
}

if(user['tipped_alot_recently'] || user['tipped_tons_recently'])
{
cb.sendNotice('User ' + user['user'] + ' (' + user['gender'] + ') is a big tipper!', cb.room_slug, '#ffffff', '#47c93e', 'bold');
}
});

cb.onLeave(function(user) {
if(!user['has_tokens']) {
cb.sendNotice('User ' + user['user'] + ' left the room!', cb.room_slug, '#ffffff', '#b71616', 'bold');
}
});
-
-

----------
-----------------------------------
--------------READ MY BIO (off)-----------
-----------------------------------

/**
Name: Do you read my bio ?
Author: grazy

Ask user to answer some question before allowing him to talk.

*/

cb.settings_choices =
[
{name: 'introduction', label: 'Introduction before the questions', type: 'str', minLength: 1, maxLength: 1000, defaultValue: 'You must answer some question before be able to talk.'},
{name: 'question1', label: 'Question 1:', type: 'str', minLength: 1, maxLength: 1000, defaultValue: 'How can you call me ? a: bb, b: with my name. (answer is "a" or "b")'},
{name: 'answer1', label: 'Answer of question 1:', type: 'str', minLength: 1, maxLength: 20, defaultValue: 'b'},
{name: 'question2', label: 'Question 2:', type: 'str', minLength: 1, maxLength: 1000, defaultValue: 'How can you meet me ? a: ask to my father, b: ask the president, c: there is no way. (answer is "a" , "b" or "c")'},
{name: 'answer2', label: 'Answer of question 2:', type: 'str', minLength: 1, maxLength: 20, defaultValue: 'c'}
];

var allowedUser = [];
var notifBack = '#FFFFFF';
var notifcolor = '#C287C2';
var notifError = '#FF0000';
var notifw = 'bold';
var questionIndexList = {};

function sendQuestion(user) {
cb.sendNotice(cb.settings.introduction,user,notifBack,notifcolor, notifw);
var questionIndex = questionIndexList[user] || 0;

if(questionIndex == 0) {
cb.sendNotice(cb.settings.question1,user,notifBack,notifcolor, notifw);
} else {
cb.sendNotice(cb.settings.question2,user,notifBack,notifcolor, notifw);
}
}

function checkAnswer(user, answer) {
var questionIndex = questionIndexList[user] || 0;
var isOk = false;
if(questionIndex == 0) {
isOk = answer == cb.settings.answer1;

} else {
isOk = answer == cb.settings.answer2;
}

if(!isOk) {
cb.sendNotice("Wrong answer!",user,notifBack,notifError, notifw);
} else {
questionIndex++;
questionIndexList[user] = questionIndex;
if(questionIndex == 2) {
// all qestion OK
delete questionIndexList[user];
allowedUser.push(user);
return true
}
}
return false;
}

cb.onMessage(function (msg)
{
if(!allowedUser.includes(msg['user'])) {
//don't print this message to chat
msg['X-Spam'] = true;

if(!checkAnswer(msg['user'], msg['m']))
{
sendQuestion(msg['user']);
}
}
});

cb.onEnter(function(user)
{
if(!allowedUser.includes(user['user'])) {
sendQuestion(user['user']);
}
});

-
----------
-----------------------------------
--------------CHESS (on)-----------
-----------------------------------
play chess on Chaturbate. It supports all the usual functionality, including:

Basic gameplay

Draws by stalemate and repetition

En passant

Castling

You can make moves in two different ways, each with the /mv command. [The broadcaster must make all moves on behalf of players.]

Algebraic notation [CASE SENSITIVE] e.g.
Qxf7+

From square to square e.g. e4 e6

Command Reference
Move a piece:
/mv e4 (algebraic)
/mv e2 e4 (from to)
Move multiple moves:
/mv e4 e6 Nf3 (algebraic only)
Undo the last move: /undo
Clear the board: /clear
Show the board: /b

SOURCE CODE:
-----------
var Chess = function(fen) {
/* jshint indent: false */

var BLACK = 'b';
var WHITE = 'w';

var EMPTY = -1;

var PAWN = 'p';
var KNIGHT = 'n';
var BISHOP = 'b';
var ROOK = 'r';
var QUEEN = 'q';
var KING = 'k';

var SYMBOLS = 'pnbrqkPNBRQK';

var DEFAULT_POSITION = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';

var POSSIBLE_RESULTS = ['1-0', '0-1', '1/2-1/2', '*'];

var PAWN_OFFSETS = {
b: [16, 32, 17, 15],
w: [-16, -32, -17, -15]
};

var PIECE_OFFSETS = {
n: [-18, -33, -31, -14, 18, 33, 31, 14],
b: [-17, -15, 17, 15],
r: [-16, 1, 16, -1],
q: [-17, -16, -15, 1, 17, 16, 15, -1],
k: [-17, -16, -15, 1, 17, 16, 15, -1]
};

var ATTACKS = [
20, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0,20, 0,
0,20, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0,20, 0, 0,
0, 0,20, 0, 0, 0, 0, 24, 0, 0, 0, 0,20, 0, 0, 0,
0, 0, 0,20, 0, 0, 0, 24, 0, 0, 0,20, 0, 0, 0, 0,
0, 0, 0, 0,20, 0, 0, 24, 0, 0,20, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,20, 2, 24, 2,20, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
24,24,24,24,24,24,56, 0, 56,24,24,24,24,24,24, 0,
0, 0, 0, 0, 0, 2,53, 56, 53, 2, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,20, 2, 24, 2,20, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,20, 0, 0, 24, 0, 0,20, 0, 0, 0, 0, 0,
0, 0, 0,20, 0, 0, 0, 24, 0, 0, 0,20, 0, 0, 0, 0,
0, 0,20, 0, 0, 0, 0, 24, 0, 0, 0, 0,20, 0, 0, 0,
0,20, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0,20, 0, 0,
20, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0,20
];

var RAYS = [
17, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 15, 0,
0, 17, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 15, 0, 0,
0, 0, 17, 0, 0, 0, 0, 16, 0, 0, 0, 0, 15, 0, 0, 0,
0, 0, 0, 17, 0, 0, 0, 16, 0, 0, 0, 15, 0, 0, 0, 0,
0, 0, 0, 0, 17, 0, 0, 16, 0, 0, 15, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 17, 0, 16, 0, 15, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 17, 16, 15, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 0, -1, -1, -1,-1, -1, -1, -1, 0,
0, 0, 0, 0, 0, 0,-15,-16,-17, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,-15, 0,-16, 0,-17, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,-15, 0, 0,-16, 0, 0,-17, 0, 0, 0, 0, 0,
0, 0, 0,-15, 0, 0, 0,-16, 0, 0, 0,-17, 0, 0, 0, 0,
0, 0,-15, 0, 0, 0, 0,-16, 0, 0, 0, 0,-17, 0, 0, 0,
0,-15, 0, 0, 0, 0, 0,-16, 0, 0, 0, 0, 0,-17, 0, 0,
-15, 0, 0, 0, 0, 0, 0,-16, 0, 0, 0, 0, 0, 0,-17
];

var SHIFTS = { p: 0, n: 1, b: 2, r: 3, q: 4, k: 5 };

var FLAGS = {
NORMAL: 'n',
CAPTURE: 'c',
BIG_PAWN: 'b',
EP_CAPTURE: 'e',
PROMOTION: 'p',
KSIDE_CASTLE: 'k',
QSIDE_CASTLE: 'q'
};

var BITS = {
NORMAL: 1,
CAPTURE: 2,
BIG_PAWN: 4,
EP_CAPTURE: 8,
PROMOTION: 16,
KSIDE_CASTLE: 32,
QSIDE_CASTLE: 64
};

var RANK_1 = 7;
var RANK_2 = 6;
var RANK_3 = 5;
var RANK_4 = 4;
var RANK_5 = 3;
var RANK_6 = 2;
var RANK_7 = 1;
var RANK_8 = 0;

var SQUARES = {
a8: 0, b8: 1, c8: 2, d8: 3, e8: 4, f8: 5, g8: 6, h8: 7,
a7: 16, b7: 17, c7: 18, d7: 19, e7: 20, f7: 21, g7: 22, h7: 23,
a6: 32, b6: 33, c6: 34, d6: 35, e6: 36, f6: 37, g6: 38, h6: 39,
a5: 48, b5: 49, c5: 50, d5: 51, e5: 52, f5: 53, g5: 54, h5: 55,
a4: 64, b4: 65, c4: 66, d4: 67, e4: 68, f4: 69, g4: 70, h4: 71,
a3: 80, b3: 81, c3: 82, d3: 83, e3: 84, f3: 85, g3: 86, h3: 87,
a2: 96, b2: 97, c2: 98, d2: 99, e2: 100, f2: 101, g2: 102, h2: 103,
a1: 112, b1: 113, c1: 114, d1: 115, e1: 116, f1: 117, g1: 118, h1: 119
};

var ROOKS = {
w: [{square: SQUARES.a1, flag: BITS.QSIDE_CASTLE},
{square: SQUARES.h1, flag: BITS.KSIDE_CASTLE}],
b: [{square: SQUARES.a8, flag: BITS.QSIDE_CASTLE},
{square: SQUARES.h8, flag: BITS.KSIDE_CASTLE}]
};

var board = new Array(128);
var kings = {w: EMPTY, b: EMPTY};
var turn = WHITE;
var castling = {w: 0, b: 0};
var ep_square = EMPTY;
var half_moves = 0;
var move_number = 1;
var history = [];
var header = {};

/* if the user passes in a fen string, load it, else default to
* starting position
*/
if (typeof fen === 'undefined') {
load(DEFAULT_POSITION);
} else {
load(fen);
}

function clear() {
board = new Array(128);
kings = {w: EMPTY, b: EMPTY};
turn = WHITE;
castling = {w: 0, b: 0};
ep_square = EMPTY;
half_moves = 0;
move_number = 1;
history = [];
header = {};
update_setup(generate_fen());
}

function reset() {
load(DEFAULT_POSITION);
}

function load(fen) {
var tokens = fen.split(/\s+/);
var position = tokens[0];
var square = 0;
var valid = SYMBOLS + '12345678/';

if (!validate_fen(fen).valid) {
return false;
}

clear();

for (var i = 0; i < position.length; i++) {
var piece = position.charAt(i);

if (piece === '/') {
square += 8;
} else if (is_digit(piece)) {
square += parseInt(piece, 10);
} else {
var color = (piece < 'a') ? WHITE : BLACK;
put({type: piece.toLowerCase(), color: color}, algebraic(square));
square++;
}
}

turn = tokens[1];

if (tokens[2].indexOf('K') > -1) {
castling.w |= BITS.KSIDE_CASTLE;
}
if (tokens[2].indexOf('Q') > -1) {
castling.w |= BITS.QSIDE_CASTLE;
}
if (tokens[2].indexOf('k') > -1) {
castling.b |= BITS.KSIDE_CASTLE;
}
if (tokens[2].indexOf('q') > -1) {
castling.b |= BITS.QSIDE_CASTLE;
}

ep_square = (tokens[3] === '-') ? EMPTY : SQUARES[tokens[3]];
half_moves = parseInt(tokens[4], 10);
move_number = parseInt(tokens[5], 10);

update_setup(generate_fen());

return true;
}

function validate_fen(fen) {
var errors = {
0: 'No errors.',
1: 'FEN string must contain six space-delimited fields.',
2: '6th field (move number) must be a positive integer.',
3: '5th field (half move counter) must be a non-negative integer.',
4: '4th field (en-passant square) is invalid.',
5: '3rd field (castling availability) is invalid.',
6: '2nd field (side to move) is invalid.',
7: '1st field (piece positions) does not contain 8 \'/\'-delimited rows.',
8: '1st field (piece positions) is invalid [consecutive numbers].',
9: '1st field (piece positions) is invalid [invalid piece].',
10: '1st field (piece positions) is invalid [row too large].',
};

/* 1st criterion: 6 space-seperated fields? */
var tokens = fen.split(/\s+/);
if (tokens.length !== 6) {
return {valid: false, error_number: 1, error: errors[1]};
}

/* 2nd criterion: move number field is a integer value > 0? */
if (isNaN(tokens[5]) || (parseInt(tokens[5], 10) <= 0)) {
return {valid: false, error_number: 2, error: errors[2]};
}

/* 3rd criterion: half move counter is an integer >= 0? */
if (isNaN(tokens[4]) || (parseInt(tokens[4], 10) < 0)) {
return {valid: false, error_number: 3, error: errors[3]};
}

/* 4th criterion: 4th field is a valid e.p.-string? */
if (!/^(-|[abcdefgh][36])$/.test(tokens[3])) {
return {valid: false, error_number: 4, error: errors[4]};
}

/* 5th criterion: 3th field is a valid castle-string? */
if( !/^(KQ?k?q?|Qk?q?|kq?|q|-)$/.test(tokens[2])) {
return {valid: false, error_number: 5, error: errors[5]};
}

/* 6th criterion: 2nd field is "w" (white) or "b" (black)? */
if (!/^(w|b)$/.test(tokens[1])) {
return {valid: false, error_number: 6, error: errors[6]};
}

/* 7th criterion: 1st field contains 8 rows? */
var rows = tokens[0].split('/');
if (rows.length !== 8) {
return {valid: false, error_number: 7, error: errors[7]};
}

/* 8th criterion: every row is valid? */
for (var i = 0; i < rows.length; i++) {
/* check for right sum of fields AND not two numbers in succession */
var sum_fields = 0;
var previous_was_number = false;

for (var k = 0; k < rows[i].length; k++) {
if (!isNaN(rows[i][k])) {
if (previous_was_number) {
return {valid: false, error_number: 8, error: errors[8]};
}
sum_fields += parseInt(rows[i][k], 10);
previous_was_number = true;
} else {
if (!/^[prnbqkPRNBQK]$/.test(rows[i][k])) {
return {valid: false, error_number: 9, error: errors[9]};
}
sum_fields += 1;
previous_was_number = false;
}
}
if (sum_fields !== 8) {
return {valid: false, error_number: 10, error: errors[10]};
}
}

/* everything's okay! */
return {valid: true, error_number: 0, error: errors[0]};
}

function generate_fen() {
var empty = 0;
var fen = '';

for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
if (board[i] == null) {
empty++;
} else {
if (empty > 0) {
fen += empty;
empty = 0;
}
var color = board[i].color;
var piece = board[i].type;

fen += (color === WHITE) ?
piece.toUpperCase() : piece.toLowerCase();
}

if ((i + 1) & 0x88) {
if (empty > 0) {
fen += empty;
}

if (i !== SQUARES.h1) {
fen += '/';
}

empty = 0;
i += 8;
}
}

var cflags = '';
if (castling[WHITE] & BITS.KSIDE_CASTLE) { cflags += 'K'; }
if (castling[WHITE] & BITS.QSIDE_CASTLE) { cflags += 'Q'; }
if (castling[BLACK] & BITS.KSIDE_CASTLE) { cflags += 'k'; }
if (castling[BLACK] & BITS.QSIDE_CASTLE) { cflags += 'q'; }

/* do we have an empty castling flag? */
cflags = cflags || '-';
var epflags = (ep_square === EMPTY) ? '-' : algebraic(ep_square);

return [fen, turn, cflags, epflags, half_moves, move_number].join(' ');
}

function set_header(args) {
for (var i = 0; i < args.length; i += 2) {
if (typeof args[i] === 'string' &&
typeof args[i + 1] === 'string') {
header[args[i]] = args[i + 1];
}
}
return header;
}

/* called when the initial board setup is changed with put() or remove().
* modifies the SetUp and FEN properties of the header object. if the FEN is
* equal to the default position, the SetUp and FEN are deleted
* the setup is only updated if history.length is zero, ie moves haven't been
* made.
*/
function update_setup(fen) {
if (history.length > 0) return;

if (fen !== DEFAULT_POSITION) {
header['SetUp'] = fen;
header['FEN'] = '1';
} else {
delete header['SetUp'];
delete header['FEN'];
}
}

function get(square) {
var piece = board[SQUARES[square]];
return (piece) ? {type: piece.type, color: piece.color} : null;
}

function put(piece, square) {
/* check for valid piece object */
if (!('type' in piece && 'color' in piece)) {
return false;
}

/* check for piece */
if (SYMBOLS.indexOf(piece.type.toLowerCase()) === -1) {
return false;
}

/* check for valid square */
if (!(square in SQUARES)) {
return false;
}

var sq = SQUARES[square];
board[sq] = {type: piece.type, color: piece.color};
if (piece.type === KING) {
kings[piece.color] = sq;
}

update_setup(generate_fen());

return true;
}

function remove(square) {
var piece = get(square);
board[SQUARES[square]] = null;
if (piece && piece.type === KING) {
kings[piece.color] = EMPTY;
}

update_setup(generate_fen());

return piece;
}

function build_move(board, from, to, flags, promotion) {
var move = {
color: turn,
from: from,
to: to,
flags: flags,
piece: board[from].type
};

if (promotion) {
move.flags |= BITS.PROMOTION;
move.promotion = promotion;
}

if (board[to]) {
move.captured = board[to].type;
} else if (flags & BITS.EP_CAPTURE) {
move.captured = PAWN;
}
return move;
}

function generate_moves(options) {
function add_move(board, moves, from, to, flags) {
/* if pawn promotion */
if (board[from].type === PAWN &&
(rank(to) === RANK_8 || rank(to) === RANK_1)) {
var pieces = [QUEEN, ROOK, BISHOP, KNIGHT];
for (var i = 0, len = pieces.length; i < len; i++) {
moves.push(build_move(board, from, to, flags, pieces[i]));
}
} else {
moves.push(build_move(board, from, to, flags));
}
}

var moves = [];
var us = turn;
var them = swap_color(us);
var second_rank = {b: RANK_7, w: RANK_2};

var first_sq = SQUARES.a8;
var last_sq = SQUARES.h1;
var single_square = false;

/* do we want legal moves? */
var legal = (typeof options !== 'undefined' && 'legal' in options) ?
options.legal : true;

/* are we generating moves for a single square? */
if (typeof options !== 'undefined' && 'square' in options) {
if (options.square in SQUARES) {
first_sq = last_sq = SQUARES[options.square];
single_square = true;
} else {
/* invalid square */
return [];
}
}

for (var i = first_sq; i <= last_sq; i++) {
/* did we run off the end of the board */
if (i & 0x88) { i += 7; continue; }

var piece = board[i];
if (piece == null || piece.color !== us) {
continue;
}

if (piece.type === PAWN) {
/* single square, non-capturing */
var square = i + PAWN_OFFSETS[us][0];
if (board[square] == null) {
add_move(board, moves, i, square, BITS.NORMAL);

/* double square */
var square = i + PAWN_OFFSETS[us][1];
if (second_rank[us] === rank(i) && board[square] == null) {
add_move(board, moves, i, square, BITS.BIG_PAWN);
}
}

/* pawn captures */
for (j = 2; j < 4; j++) {
var square = i + PAWN_OFFSETS[us][j];
if (square & 0x88) continue;

if (board[square] != null &&
board[square].color === them) {
add_move(board, moves, i, square, BITS.CAPTURE);
} else if (square === ep_square) {
add_move(board, moves, i, ep_square, BITS.EP_CAPTURE);
}
}
} else {
for (var j = 0, len = PIECE_OFFSETS[piece.type].length; j < len; j++) {
var offset = PIECE_OFFSETS[piece.type][j];
var square = i;

while (true) {
square += offset;
if (square & 0x88) break;

if (board[square] == null) {
add_move(board, moves, i, square, BITS.NORMAL);
} else {
if (board[square].color === us) break;
add_move(board, moves, i, square, BITS.CAPTURE);
break;
}

/* break, if knight or king */
if (piece.type === 'n' || piece.type === 'k') break;
}
}
}
}

/* check for castling if: a) we're generating all moves, or b) we're doing
* single square move generation on the king's square
*/
if ((!single_square) || last_sq === kings[us]) {
/* king-side castling */
if (castling[us] & BITS.KSIDE_CASTLE) {
var castling_from = kings[us];
var castling_to = castling_from + 2;

if (board[castling_from + 1] == null &&
board[castling_to] == null &&
!attacked(them, kings[us]) &&
!attacked(them, castling_from + 1) &&
!attacked(them, castling_to)) {
add_move(board, moves, kings[us] , castling_to,
BITS.KSIDE_CASTLE);
}
}

/* queen-side castling */
if (castling[us] & BITS.QSIDE_CASTLE) {
var castling_from = kings[us];
var castling_to = castling_from - 2;

if (board[castling_from - 1] == null &&
board[castling_from - 2] == null &&
board[castling_from - 3] == null &&
!attacked(them, kings[us]) &&
!attacked(them, castling_from - 1) &&
!attacked(them, castling_to)) {
add_move(board, moves, kings[us], castling_to,
BITS.QSIDE_CASTLE);
}
}
}

/* return all pseudo-legal moves (this includes moves that allow the king
* to be captured)
*/
if (!legal) {
return moves;
}

/* filter out illegal moves */
var legal_moves = [];
for (var i = 0, len = moves.length; i < len; i++) {
make_move(moves[i]);
if (!king_attacked(us)) {
legal_moves.push(moves[i]);
}
undo_move();
}

return legal_moves;
}

/* convert a move from 0x88 coordinates to Standard Algebraic Notation
* (SAN)
*/
function move_to_san(move) {
var output = '';

if (move.flags & BITS.KSIDE_CASTLE) {
output = 'O-O';
} else if (move.flags & BITS.QSIDE_CASTLE) {
output = 'O-O-O';
} else {
var disambiguator = get_disambiguator(move);

if (move.piece !== PAWN) {
output += move.piece.toUpperCase() + disambiguator;
}

if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) {
if (move.piece === PAWN) {
output += algebraic(move.from)[0];
}
output += 'x';
}

output += algebraic(move.to);

if (move.flags & BITS.PROMOTION) {
output += '=' + move.promotion.toUpperCase();
}
}

make_move(move);
if (in_check()) {
if (in_checkmate()) {
output += '#';
} else {
output += '+';
}
}
undo_move();

return output;
}

function attacked(color, square) {
for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
/* did we run off the end of the board */
if (i & 0x88) { i += 7; continue; }

/* if empty square or wrong color */
if (board[i] == null || board[i].color !== color) continue;

var piece = board[i];
var difference = i - square;
var index = difference + 119;

if (ATTACKS[index] & (1 << SHIFTS[piece.type])) {
if (piece.type === PAWN) {
if (difference > 0) {
if (piece.color === WHITE) return true;
} else {
if (piece.color === BLACK) return true;
}
continue;
}

/* if the piece is a knight or a king */
if (piece.type === 'n' || piece.type === 'k') return true;

var offset = RAYS[index];
var j = i + offset;

var blocked = false;
while (j !== square) {
if (board[j] != null) { blocked = true; break; }
j += offset;
}

if (!blocked) return true;
}
}

return false;
}

function king_attacked(color) {
return attacked(swap_color(color), kings[color]);
}

function in_check() {
return king_attacked(turn);
}

function in_checkmate() {
return in_check() && generate_moves().length === 0;
}

function in_stalemate() {
return !in_check() && generate_moves().length === 0;
}

function insufficient_material() {
var pieces = {};
var bishops = [];
var num_pieces = 0;
var sq_color = 0;

for (var i = SQUARES.a8; i<= SQUARES.h1; i++) {
sq_color = (sq_color + 1) % 2;
if (i & 0x88) { i += 7; continue; }

var piece = board[i];
if (piece) {
pieces[piece.type] = (piece.type in pieces) ?
pieces[piece.type] + 1 : 1;
if (piece.type === BISHOP) {
bishops.push(sq_color);
}
num_pieces++;
}
}

/* k vs. k */
if (num_pieces === 2) { return true; }

/* k vs. kn .... or .... k vs. kb */
else if (num_pieces === 3 && (pieces[BISHOP] === 1 ||
pieces[KNIGHT] === 1)) { return true; }

/* kb vs. kb where any number of bishops are all on the same color */
else if (num_pieces === pieces[BISHOP] + 2) {
var sum = 0;
var len = bishops.length;
for (var i = 0; i < len; i++) {
sum += bishops[i];
}
if (sum === 0 || sum === len) { return true; }
}

return false;
}

function in_threefold_repetition() {
/* TODO: while this function is fine for casual use, a better
* implementation would use a Zobrist key (instead of FEN). the
* Zobrist key would be maintained in the make_move/undo_move functions,
* avoiding the costly that we do below.
*/
var moves = [];
var positions = {};
var repetition = false;

while (true) {
var move = undo_move();
if (!move) break;
moves.push(move);
}

while (true) {
/* remove the last two fields in the FEN string, they're not needed
* when checking for draw by rep */
var fen = generate_fen().split(' ').slice(0,4).join(' ');

/* has the position occurred three or move times */
positions[fen] = (fen in positions) ? positions[fen] + 1 : 1;
if (positions[fen] >= 3) {
repetition = true;
}

if (!moves.length) {
break;
}
make_move(moves.pop());
}

return repetition;
}

function push(move) {
history.push({
move: move,
kings: {b: kings.b, w: kings.w},
turn: turn,
castling: {b: castling.b, w: castling.w},
ep_square: ep_square,
half_moves: half_moves,
move_number: move_number
});
}

function make_move(move) {
var us = turn;
var them = swap_color(us);
push(move);

board[move.to] = board[move.from];
board[move.from] = null;

/* if ep capture, remove the captured pawn */
if (move.flags & BITS.EP_CAPTURE) {
if (turn === BLACK) {
board[move.to - 16] = null;
} else {
board[move.to + 16] = null;
}
}

/* if pawn promotion, replace with new piece */
if (move.flags & BITS.PROMOTION) {
board[move.to] = {type: move.promotion, color: us};
}

/* if we moved the king */
if (board[move.to].type === KING) {
kings[board[move.to].color] = move.to;

/* if we castled, move the rook next to the king */
if (move.flags & BITS.KSIDE_CASTLE) {
var castling_to = move.to - 1;
var castling_from = move.to + 1;
board[castling_to] = board[castling_from];
board[castling_from] = null;
} else if (move.flags & BITS.QSIDE_CASTLE) {
var castling_to = move.to + 1;
var castling_from = move.to - 2;
board[castling_to] = board[castling_from];
board[castling_from] = null;
}

/* turn off castling */
castling[us] = '';
}

/* turn off castling if we move a rook */
if (castling[us]) {
for (var i = 0, len = ROOKS[us].length; i < len; i++) {
if (move.from === ROOKS[us][i].square &&
castling[us] & ROOKS[us][i].flag) {
castling[us] ^= ROOKS[us][i].flag;
break;
}
}
}

/* turn off castling if we capture a rook */
if (castling[them]) {
for (var i = 0, len = ROOKS[them].length; i < len; i++) {
if (move.to === ROOKS[them][i].square &&
castling[them] & ROOKS[them][i].flag) {
castling[them] ^= ROOKS[them][i].flag;
break;
}
}
}

/* if big pawn move, update the en passant square */
if (move.flags & BITS.BIG_PAWN) {
if (turn === 'b') {
ep_square = move.to - 16;
} else {
ep_square = move.to + 16;
}
} else {
ep_square = EMPTY;
}

/* reset the 50 move counter if a pawn is moved or a piece is captured */
if (move.piece === PAWN) {
half_moves = 0;
} else if (move.flags & (BITS.CAPTURE | BITS.EP_CAPTURE)) {
half_moves = 0;
} else {
half_moves++;
}

if (turn === BLACK) {
move_number++;
}
turn = swap_color(turn);
}

function undo_move() {
var old = history.pop();
if (old == null) { return null; }

var move = old.move;
kings = old.kings;
turn = old.turn;
castling = old.castling;
ep_square = old.ep_square;
half_moves = old.half_moves;
move_number = old.move_number;

var us = turn;
var them = swap_color(turn);

board[move.from] = board[move.to];
board[move.from].type = move.piece; // to undo any promotions
board[move.to] = null;

if (move.flags & BITS.CAPTURE) {
board[move.to] = {type: move.captured, color: them};
} else if (move.flags & BITS.EP_CAPTURE) {
var index;
if (us === BLACK) {
index = move.to - 16;
} else {
index = move.to + 16;
}
board[index] = {type: PAWN, color: them};
}

if (move.flags & (BITS.KSIDE_CASTLE | BITS.QSIDE_CASTLE)) {
var castling_to, castling_from;
if (move.flags & BITS.KSIDE_CASTLE) {
castling_to = move.to + 1;
castling_from = move.to - 1;
} else if (move.flags & BITS.QSIDE_CASTLE) {
castling_to = move.to - 2;
castling_from = move.to + 1;
}

board[castling_to] = board[castling_from];
board[castling_from] = null;
}

return move;
}

/* this function is used to uniquely identify ambiguous moves */
function get_disambiguator(move) {
var moves = generate_moves();

var from = move.from;
var to = move.to;
var piece = move.piece;

var ambiguities = 0;
var same_rank = 0;
var same_file = 0;

for (var i = 0, len = moves.length; i < len; i++) {
var ambig_from = moves[i].from;
var ambig_to = moves[i].to;
var ambig_piece = moves[i].piece;

/* if a move of the same piece type ends on the same to square, we'll
* need to add a disambiguator to the algebraic notation
*/
if (piece === ambig_piece && from !== ambig_from && to === ambig_to) {
ambiguities++;

if (rank(from) === rank(ambig_from)) {
same_rank++;
}

if (file(from) === file(ambig_from)) {
same_file++;
}
}
}

if (ambiguities > 0) {
/* if there exists a similar moving piece on the same rank and file as
* the move in question, use the square as the disambiguator
*/
if (same_rank > 0 && same_file > 0) {
return algebraic(from);
}
/* if the moving piece rests on the same file, use the rank symbol as the
* disambiguator
*/
else if (same_file > 0) {
return algebraic(from).charAt(1);
}
/* else use the file symbol */
else {
return algebraic(from).charAt(0);
}
}

return '';
}

function ascii() {
var s = ' +------------------------+\n';
for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
/* display the rank */
if (file(i) === 0) {
s += ' ' + '87654321'[rank(i)] + ' |';
}

/* empty piece */
if (board[i] == null) {
s += ' . ';
} else {
var piece = board[i].type;
var color = board[i].color;
var symbol = (color === WHITE) ?
piece.toUpperCase() : piece.toLowerCase();
s += ' ' + symbol + ' ';
}

if ((i + 1) & 0x88) {
s += '|\n';
i += 8;
}
}
s += ' +------------------------+\n';
s += ' a b c d e f g h\n';

return s;
}

function square_color(sq_0x88) {
return ((rank(sq_0x88) + file(sq_0x88)) % 2 === 0) ? 'w' : 'b';
}

function stat() {
var o = '';
for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
var bg = square_color(i);
var p = '';
var c = '';
if (board[i] != null) {
p = board[i].type;
c = board[i].color;
}
o += imagefor(bg, p, c) + ' ';
if ((i + 1) & 0x88 && rank(i) < 7) {
o += '\n';
i += 8;
}
}
return o;
}

/*****************************************************************************
* UTILITY FUNCTIONS
****************************************************************************/
function rank(i) {
return i >> 4;
}

function file(i) {
return i & 15;
}

function algebraic(i){
var f = file(i), r = rank(i);
return 'abcdefgh'.substring(f,f+1) + '87654321'.substring(r,r+1);
}

function swap_color(c) {
return c === WHITE ? BLACK : WHITE;
}

function is_digit(c) {
return '0123456789'.indexOf(c) !== -1;
}

/* pretty = external move object */
function make_pretty(ugly_move) {
var move = clone(ugly_move);
move.san = move_to_san(move);
move.to = algebraic(move.to);
move.from = algebraic(move.from);

var flags = '';

for (var flag in BITS) {
if (BITS[flag] & move.flags) {
flags += FLAGS[flag];
}
}
move.flags = flags;

return move;
}

function clone(obj) {
var dupe = (obj instanceof Array) ? [] : {};

for (var property in obj) {
if (typeof property === 'object') {
dupe[property] = clone(obj[property]);
} else {
dupe[property] = obj[property];
}
}

return dupe;
}

function trim(str) {
return str.replace(/^\s+|\s+$/g, '');
}

/*****************************************************************************
* DEBUGGING UTILITIES
****************************************************************************/
function perft(depth) {
var moves = generate_moves({legal: false});
var nodes = 0;
var color = turn;

for (var i = 0, len = moves.length; i < len; i++) {
make_move(moves[i]);
if (!king_attacked(color)) {
if (depth - 1 > 0) {
var child_nodes = perft(depth - 1);
nodes += child_nodes;
} else {
nodes++;
}
}
undo_move();
}

return nodes;
}

return {
/***************************************************************************
* PUBLIC CONSTANTS (is there a better way to do this?)
**************************************************************************/
WHITE: WHITE,
BLACK: BLACK,
PAWN: PAWN,
KNIGHT: KNIGHT,
BISHOP: BISHOP,
ROOK: ROOK,
QUEEN: QUEEN,
KING: KING,
SQUARES: (function() {
/* from the ECMA-262 spec (section 12.6.4):
* "The mechanics of enumerating the properties ... is
* implementation dependent"
* so: for (var sq in SQUARES) { keys.push(sq); } might not be
* ordered correctly
*/
var keys = [];
for (var i = SQUARES.a8; i <= SQUARES.h1; i++) {
if (i & 0x88) { i += 7; continue; }
keys.push(algebraic(i));
}
return keys;
})(),
FLAGS: FLAGS,

/***************************************************************************
* PUBLIC API
**************************************************************************/
load: function(fen) {
return load(fen);
},

reset: function() {
return reset();
},

moves: function(options) {
/* The internal representation of a chess move is in 0x88 format, and
* not meant to be human-readable. The code below converts the 0x88
* square coordinates to algebraic coordinates. It also prunes an
* unnecessary move keys resulting from a verbose call.
*/

var ugly_moves = generate_moves(options);
var moves = [];

for (var i = 0, len = ugly_moves.length; i < len; i++) {

/* does the user want a full move object (most likely not), or just
* SAN
*/
if (typeof options !== 'undefined' && 'verbose' in options &&
options.verbose) {
moves.push(make_pretty(ugly_moves[i]));
} else {
moves.push(move_to_san(ugly_moves[i]));
}
}

return moves;
},

in_check: function() {
return in_check();
},

in_checkmate: function() {
return in_checkmate();
},

in_stalemate: function() {
return in_stalemate();
},

in_draw: function() {
return half_moves >= 100 ||
in_stalemate() ||
insufficient_material() ||
in_threefold_repetition();
},

insufficient_material: function() {
return insufficient_material();
},

in_threefold_repetition: function() {
return in_threefold_repetition();
},

game_over: function() {
return half_moves >= 100 ||
in_checkmate() ||
in_stalemate() ||
insufficient_material() ||
in_threefold_repetition();
},

validate_fen: function(fen) {
return validate_fen(fen);
},

fen: function() {
return generate_fen();
},

pgn: function(options) {
/* using the specification from http://www.chessclub.com/help/PGN-spec
* example for html usage: .pgn({ max_width: 72, newline_char: "
" })
*/
var newline = (typeof options === 'object' &&
typeof options.newline_char === 'string') ?
options.newline_char : '\n';
var max_width = (typeof options === 'object' &&
typeof options.max_width === 'number') ?
options.max_width : 0;
var result = [];
var header_exists = false;

/* add the PGN header headerrmation */
for (var i in header) {
/* TODO: order of enumerated properties in header object is not
* guaranteed, see ECMA-262 spec (section 12.6.4)
*/
result.push('[' + i + ' \"' + header[i] + '\"]' + newline);
header_exists = true;
}

if (header_exists && history.length) {
result.push(newline);
}

/* pop all of history onto reversed_history */
var reversed_history = [];
while (history.length > 0) {
reversed_history.push(undo_move());
}

var moves = [];
var move_string = '';
var pgn_move_number = 1;

/* build the list of moves. a move_string looks like: "3. e3 e6" */
while (reversed_history.length > 0) {
var move = reversed_history.pop();

/* if the position started with black to move, start PGN with 1. ... */
if (pgn_move_number === 1 && move.color === 'b') {
move_string = '1. ...';
pgn_move_number++;
} else if (move.color === 'w') {
/* store the previous generated move_string if we have one */
if (move_string.length) {
moves.push(move_string);
}
move_string = pgn_move_number + '.';
pgn_move_number++;
}

move_string = move_string + ' ' + move_to_san(move);
make_move(move);
}

/* are there any other leftover moves? */
if (move_string.length) {
moves.push(move_string);
}

/* is there a result? */
if (typeof header.Result !== 'undefined') {
moves.push(header.Result);
}

/* history should be back to what is was before we started generating PGN,
* so join together moves
*/
if (max_width === 0) {
return result.join('') + moves.join(' ');
}

/* wrap the PGN output at max_width */
var current_width = 0;
for (var i = 0; i < moves.length; i++) {
/* if the current move will push past max_width */
if (current_width + moves[i].length > max_width && i !== 0) {

/* don't end the line with whitespace */
if (result[result.length - 1] === ' ') {
result.pop();
}

result.push(newline);
current_width = 0;
} else if (i !== 0) {
result.push(' ');
current_width++;
}
result.push(moves[i]);
current_width += moves[i].length;
}

return result.join('');
},

load_pgn: function(pgn, options) {
function mask(str) {
return str.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
}

/* convert a move from Standard Algebraic Notation (SAN) to 0x88
* coordinates
*/
function move_from_san(move) {
var to, from, flags = BITS.NORMAL, promotion;
var parse = move.match(/^([NBKRQ])?([abcdefgh12345678][12345678]?)?(x)?([abcdefgh][12345678])(=?[NBRQ])?/);
if (move.slice(0, 5) === 'O-O-O') {
from = kings[turn];
to = from - 2;
flags = BITS.QSIDE_CASTLE;
} else if (move.slice(0, 3) === 'O-O') {
from = kings[turn];
to = from + 2;
flags = BITS.KSIDE_CASTLE;
} else if (parse && parse[1]) {
// regular moves
var piece = parse[1].toLowerCase();
if (parse[3]) {
// capture
flags = BITS.CAPTURE;
}
to = SQUARES[parse[4]];
for (var j = 0, len = PIECE_OFFSETS[piece].length; j < len; j++) {
var offset = PIECE_OFFSETS[piece][j];
var square = to;

while (true) {
square += offset;
if (square & 0x88) break;

var b = board[square];
if (b) {
if (b.color === turn && b.type === piece && (!parse[2] || algebraic(square).indexOf(parse[2]) >= 0)) {
from = square;
}
break;
}

/* break, if knight or king */
if (piece === 'n' || piece === 'k') break;
}
}
} else if (parse) {
// pawn move
if (parse[3]) {
// capture
to = SQUARES[parse[4]];
for (var j = 2; j < 4; j++) {
var square = to - PAWN_OFFSETS[turn][j];
if (square & 0x88) continue;

if (board[square] != null &&
board[square].color === turn &&
algebraic(square)[0] === parse[2]) {
from = square;
}
}
if (board[to]) {
flags = BITS.CAPTURE;
} else {
flags = BITS.EP_CAPTURE;
}
} else {
// normal move
to = SQUARES[move.slice(0,2)];
var c = to - PAWN_OFFSETS[turn][0],
b = board[c];
if (b && b.type === PAWN && b.color === turn) {
from = c;
} else {
c = to - PAWN_OFFSETS[turn][1];
b = board[c];
if (b && b.type === PAWN && b.color === turn) {
from = c;
flags = BITS.BIG_PAWN;
}
}
}
// promotion?
if (parse[5]) {
if(typeof parse[5][1] == 'undefined') {
promotion = parse[5][0].toLowerCase();
} else {
promotion = parse[5][1].toLowerCase();
}
}
}
if (from >=0 && to >=0 && flags) {
return build_move(board, from, to, flags, promotion);
} else if (move.length > 0) {
/* alert(move); // error in PGN, or in parsing. */
}
}

function get_move_obj(move) {
return move_from_san(trim(move));
}

function has_keys(object) {
var has_keys = false;
for (var key in object) {
has_keys = true;
}
return has_keys;
}

function parse_pgn_header(header, options) {
var newline_char = (typeof options === 'object' &&
typeof options.newline_char === 'string') ?
options.newline_char : '\r?\n';
var header_obj = {};
var headers = header.split(newline_char);
var key = '';
var value = '';

for (var i = 0; i < headers.length; i++) {
key = headers[i].replace(/^\[([A-Z][A-Za-z]*)\s.*\]$/, '$1');
value = headers[i].replace(/^\[[A-Za-z]+\s"(.*)"\]$/, '$1');
if (trim(key).length > 0) {
header_obj[key] = value;
}
}

return header_obj;
}

var newline_char = (typeof options === 'object' &&
typeof options.newline_char === 'string') ?
options.newline_char : '\r?\n';
var regex = new RegExp('^(\\[(.|' + mask(newline_char) + ')*\\])' +
'(' + mask(newline_char) + ')*' +
'1.(' + mask(newline_char) + '|.)*$', 'g');

/* get header part of the PGN file */
var header_string = pgn.replace(regex, '$1');

/* no info part given, begins with moves */
if (header_string[0] !== '[') {
header_string = '';
}

reset();

/* parse PGN header */
var headers = parse_pgn_header(header_string, options);
for (var key in headers) {
set_header([key, headers[key]]);
}

/* delete header to get the moves */
var ms = pgn.replace(header_string, '').replace(new RegExp(mask(newline_char), 'g'), ' ');

/* delete comments */
ms = ms.replace(/(\{[^}]+\})+?/g, '');

/* delete move numbers */
ms = ms.replace(/\d+\./g, '');

/* trim and get array of moves */
var moves = trim(ms).split(new RegExp(/\s+/));

/* delete empty entries */
moves = moves.join(',').replace(/,,+/g, ',').split(',');
var move = '';

for (var half_move = 0; half_move < moves.length - 1; half_move++) {
move = get_move_obj(moves[half_move]);

/* move not possible! (don't clear the board to examine to show the
* latest valid position)
*/
if (move == null) {
return false;
} else {
make_move(move);
}
}

/* examine last move */
move = moves[moves.length - 1];
if (POSSIBLE_RESULTS.indexOf(move) > -1) {
if (has_keys(header) && typeof header.Result === 'undefined') {
set_header(['Result', move]);
}
}
else {
move = get_move_obj(move);
if (move == null) {
return false;
} else {
make_move(move);
}
}
return true;
},

header: function() {
return header;
},

ascii: function() {
return ascii();
},

turn: function() {
return turn;
},

move: function(move) {
/* The move function can be called with in the following parameters:
*
* .move('Nxb7') <- where 'move' is a case-sensitive SAN string
*
* .move({ from: 'h7', <- where the 'move' is a move object (additional
* to :'h8', fields are ignored)
* promotion: 'q',
* })
*/
var move_obj = null;
var moves = generate_moves();

if (typeof move === 'string') {
/* convert the move string to a move object */
for (var i = 0, len = moves.length; i < len; i++) {
if (move === move_to_san(moves[i])) {
move_obj = moves[i];
break;
}
}
} else if (typeof move === 'object') {
/* convert the pretty move object to an ugly move object */
for (var i = 0, len = moves.length; i < len; i++) {
if (move.from === algebraic(moves[i].from) &&
move.to === algebraic(moves[i].to) &&
(!('promotion' in moves[i]) ||
move.promotion === moves[i].promotion)) {
move_obj = moves[i];
break;
}
}
}

/* failed to find move */
if (!move_obj) {
return null;
}

/* need to make a copy of move because we can't generate SAN after the
* move is made
*/
var pretty_move = make_pretty(move_obj);

make_move(move_obj);

return pretty_move;
},

undo: function() {
var move = undo_move();
return (move) ? make_pretty(move) : null;
},

clear: function() {
return clear();
},

put: function(piece, square) {
return put(piece, square);
},

get: function(square) {
return get(square);
},

remove: function(square) {
return remove(square);
},

perft: function(depth) {
return perft(depth);
},

square_color: function(square) {
if (square in SQUARES) {
var sq_0x88 = SQUARES[square];
return ((rank(sq_0x88) + file(sq_0x88)) % 2 === 0) ? 'w' : 'b';
}

return null;
},

history: function(options) {
var reversed_history = [];
var move_history = [];
var verbose = (typeof options !== 'undefined' && 'verbose' in options &&
options.verbose);

while (history.length > 0) {
reversed_history.push(undo_move());
}

while (reversed_history.length > 0) {
var move = reversed_history.pop();
if (verbose) {
move_history.push(make_pretty(move));
} else {
move_history.push(move_to_san(move));
}
make_move(move);
}

return move_history;
},

stat: function() {
cb.sendNotice(stat());
if (chess.game_over()) {
var n = '';
if (chess.in_draw()) {
n += 'Draw!';
if (chess.in_stalemate()) {
n += ' By Stalemate.';
}
else if (chess.in_threefold_repetition()) {
n += ' By threefold repetition.';
}
}
else if (chess.in_checkmate()) {
if (chess.turn() == chess.BLACK) {
n += 'White wins.';
} else {
n += 'Black wins.';
}
n += ' Checkmate!';
}
n += ' Game has ended.';
cb.sendNotice(n);
cb.sendNotice('Moves ' + chess.history().join(' '));
}
else if (chess.in_check()) {
cb.sendNotice("Check!");
}
}

};
};

function imagefor(bg, c, p) {
if (c == '') {
c = '_';
}
if (p == '') {
p = '_';
}
return ':chess_' + bg + '_' + c + '_' + p;
}

function isBroadcaster(m) {
return m.user == cb.room_slug || m.user == 'orwhen' || m.is_mod;
}

function iswhitesq(i) {
return (i + chess.rank(i)) % 2 == 1;
}

var chess = Chess();

function domove(ms) {
var newmv = chess.move(ms);
if (newmv == null) {
newmv = chess.move(ms + '+');
}
if (newmv == null) {
newmv = chess.move(ms + '#');
}
if (newmv == null) {
var ps = ms.split(' ');
mv = {
'from': ps[0],
'to': ps[1],
'promotion': ps[2]
};
newmv = chess.move(mv);
}
if (newmv != null) {
cb.sendNotice("Moved " + imagefor('w', newmv.piece, newmv.color) + " from " + newmv.from + " to " + newmv.to);
} else {
cb.sendNotice("Invalid move '" + ms + "'");
}
return newmv;
}

function intro() {
return '#chess (' + cb.settings.tokens_for_move + ' tokens for a move)';
}

cb.onMessage(function(m) {
if (isBroadcaster(m) && m.m.indexOf('/mv ') == 0) {
var ms = m.m.substring(4);
var newmv = domove(ms);
if (newmv != null) {
chess.stat();
}
}
if (isBroadcaster(m) && m.m.indexOf('/mvs ') == 0) {
var mss = m.m.substring(5);
var mvs = mss.split(' ');
for (var i = 0; i < mvs.length; i++) {
domove(mvs[i]);
}
chess.stat();
}
if (isBroadcaster(m) && m.m.indexOf('/clear') == 0) {
chess.reset();
cb.sendNotice("Cleared board.");
chess.stat();
}
if (isBroadcaster(m) && m.m.indexOf('/undo') == 0) {
chess.undo();
chess.stat();
cb.sendNotice("Undid one move.");
}
if (isBroadcaster(m) && m.m.indexOf('/b') == 0) {
chess.stat();
}
});

cb.onTip(function(t) {
if (t.amount >= cb.settings.tokens_for_move) {
k = imagefor('w', 'b', 'k');
cb.sendNotice(k + ' ' + t.from_user + ' ' + k + ' has earned a move. Play!');
}
});

cb.onEnter(function(u) {
cb.sendNotice(intro(), u.user);
});

cb.settings_choices = [{
name:'tokens_for_move',
type:'int',
minValue:1,
maxValue:999,
defaultValue:20,
label: "Tokens to make a move."
}]

cb.changeRoomSubject(intro());
--------------
--------------------------
--------------------

© Copyright Freesexcam 2011- 2024. All Rights Reserved.