<commit_before>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError, OrderNotFound } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class bitflyer extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'bitflyer',
            'name': 'bitFlyer',
            'countries': 'JP',
            'version': 'v1',
            'rateLimit': 500,
            'has': {
                'CORS': false,
                'withdraw': true,
                'fetchOrders': true,
                'fetchOrder': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/28051642-56154182-660e-11e7-9b0d-6042d1e6edd8.jpg',
                'api': 'https://api.bitflyer.jp',
                'www': 'https://bitflyer.jp',
                'doc': 'https://bitflyer.jp/API',
            },
            'api': {
                'public': {
                    'get': [
                        'getmarkets/usa', // new (wip)
                        'getmarkets/eu',  // new (wip)
                        'getmarkets',     // or 'markets'
                        'getboard',       // ...
                        'getticker',
                        'getexecutions',
                        'gethealth',
                        'getchats',
                    ],
                },
                'private': {
                    'get': [
                        'getpermissions',
                        'getbalance',
                        'getcollateral',
                        'getcollateralaccounts',
                        'getaddresses',
                        'getcoinins',
                        'getcoinouts',
                        'getbankaccounts',
                        'getdeposits',
                        'getwithdrawals',
                        'getchildorders',
                        'getparentorders',
                        'getparentorder',
                        'getexecutions',
                        'getpositions',
                        'gettradingcommission',
                    ],
                    'post': [
                        'sendcoin',
                        'withdraw',
                        'sendchildorder',
                        'cancelchildorder',
                        'sendparentorder',
                        'cancelparentorder',
                        'cancelallchildorders',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.25 / 100,
                    'taker': 0.25 / 100,
                },
            },
        });
    }

    async fetchMarkets () {
        let jp_markets = await this.publicGetGetmarkets ();
        let us_markets = await this.publicGetGetmarketsUsa ();
        let eu_markets = await this.publicGetGetmarketsEu ();
        let markets = this.arrayConcat (jp_markets, us_markets);
        markets = this.arrayConcat (markets, eu_markets);
        let result = [];
        for (let p = 0; p < markets.length; p++) {
            let market = markets[p];
            let id = market['product_code'];
            let currencies = id.split ('_');
            let base = undefined;
            let quote = undefined;
            let symbol = id;
            let numCurrencies = currencies.length;
            if (numCurrencies === 1) {
                base = symbol.slice (0, 3);
                quote = symbol.slice (3, 6);
            } else if (numCurrencies === 2) {
                base = currencies[0];
                quote = currencies[1];
                symbol = base + '/' + quote;
            } else {
                base = currencies[1];
                quote = currencies[2];
            }
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'info': market,
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privateGetGetbalance ();
        let balances = {};
        for (let b = 0; b < response.length; b++) {
            let account = response[b];
            let currency = account['currency_code'];
            balances[currency] = account;
        }
        let result = { 'info': response };
        let currencies = Object.keys (this.currencies);
        for (let i = 0; i < currencies.length; i++) {
            let currency = currencies[i];
            let account = this.account ();
            if (currency in balances) {
                account['total'] = balances[currency]['amount'];
                account['free'] = balances[currency]['available'];
                account['used'] = account['total'] - account['free'];
            }
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let orderbook = await this.publicGetGetboard (this.extend ({
            'product_code': this.marketId (symbol),
        }, params));
        return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'size');
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let ticker = await this.publicGetGetticker (this.extend ({
            'product_code': this.marketId (symbol),
        }, params));
        let timestamp = this.parse8601 (ticker['timestamp']);
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': undefined,
            'low': undefined,
            'bid': parseFloat (ticker['best_bid']),
            'ask': parseFloat (ticker['best_ask']),
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': parseFloat (ticker['ltp']),
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': parseFloat (ticker['volume_by_product']),
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    parseTrade (trade, market = undefined) {
        let side = undefined;
        let order = undefined;
        if ('side' in trade)
            if (trade['side']) {
                side = trade['side'].toLowerCase ();
                let id = side + '_child_order_acceptance_id';
                if (id in trade)
                    order = trade[id];
            }
        let timestamp = this.parse8601 (trade['exec_date']);
        return {
            'id': trade['id'].toString (),
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'order': order,
            'type': undefined,
            'side': side,
            'price': trade['price'],
            'amount': trade['size'],
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetGetexecutions (this.extend ({
            'product_code': market['id'],
        }, params));
        return this.parseTrades (response, market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let order = {
            'product_code': this.marketId (symbol),
            'child_order_type': type.toUpperCase (),
            'side': side.toUpperCase (),
            'price': price,
            'size': amount,
        };
        let result = await this.privatePostSendchildorder (this.extend (order, params));
        return {
            'info': result,
            'id': result['child_order_acceptance_id'],
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        if (typeof symbol === 'undefined')
            throw new ExchangeError (this.id + ' cancelOrder() requires a symbol argument');
        await this.loadMarkets ();
        return await this.privatePostCancelchildorder (this.extend ({
            'product_code': this.marketId (symbol),
            'child_order_acceptance_id': id,
        }, params));
    }

    parseOrderStatus (status) {
        let statuses = {
            'ACTIVE': 'open',
            'COMPLETED': 'closed',
            'CANCELED': 'canceled',
            'EXPIRED': 'canceled',
            'REJECTED': 'canceled',
        };
        return (status in statuses) ? statuses[status] : status.toLowerCase ();
    }

    parseOrder (order, market = undefined) {
        let timestamp = this.parse8601 (order['child_order_date']);
        let amount = this.safeFloat (order, 'size');
        let remaining = this.safeFloat (order, 'outstanding_size');
        let filled = this.safeFloat (order, 'executed_size');
        let price = this.safeFloat (order, 'price');
        let cost = price * filled;
        return {
            'id': order['child_order_id'],
            'info': order,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'status': this.parseOrderStatus(order['child_order_state']),
            'symbol': market,
            'type': order['child_order_type'].toLowerCase (),
            'side': order['side'].toLowerCase (),
            'price': price,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'fee': this.safeFloat (order, 'total_commission'),
        };
    }

    async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        if (typeof symbol === 'undefined')
            throw new ExchangeError (this.id + ' cancelOrder() requires a symbol argument');
        let count = 100;
        if(limit)
            count = limit;
        await this.loadMarkets ();
        let response = await this.privateGetGetchildorders (this.extend ({
            'product_code': this.marketId (symbol),
            'count': count,
        }, params));
        let orders = this.parseOrders (response, symbol, since, limit);
        if (symbol)
            orders = this.filterBy (orders, 'symbol', symbol);
        return orders;
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        if (typeof symbol === 'undefined')
            throw new ExchangeError (this.id + ' cancelOrder() requires a symbol argument');
        let orders = await this.fetchOrders (symbol);
        for(let i = 0; i < orders.length; i++) {
            let order = orders[i]
            if(order['id'] == id)
                return order;
        }
        throw new OrderNotFound (this.id + ' No matching order found: ' + id);
    }

    async withdraw (currency, amount, address, tag = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostWithdraw (this.extend ({
            'currency_code': currency,
            'amount': amount,
            // 'bank_account_id': 1234,
        }, params));
        return {
            'info': response,
            'id': response['message_id'],
        };
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let request = '/' + this.version + '/';
        if (api === 'private')
            request += 'me/';
        request += path;
        if (method === 'GET') {
            if (Object.keys (params).length)
                request += '?' + this.urlencode (params);
        }
        let url = this.urls['api'] + request;
        if (api === 'private') {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            let auth = [ nonce, method, request ].join ('');
            if (Object.keys (params).length) {
                body = this.json (params);
                if (method !== 'GET')
                    auth += body;
            }
            headers = {
                'ACCESS-KEY': this.apiKey,
                'ACCESS-TIMESTAMP': nonce,
                'ACCESS-SIGN': this.hmac (this.encode (auth), this.encode (this.secret)),
                'Content-Type': 'application/json',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }
};
<commit_msg>fixed with messy bitflyer id system... ffs japanese engineers<commit_after>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError, OrderNotFound } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class bitflyer extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'bitflyer',
            'name': 'bitFlyer',
            'countries': 'JP',
            'version': 'v1',
            'rateLimit': 500,
            'has': {
                'CORS': false,
                'withdraw': true,
                'fetchOrders': true,
                'fetchOrder': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/28051642-56154182-660e-11e7-9b0d-6042d1e6edd8.jpg',
                'api': 'https://api.bitflyer.jp',
                'www': 'https://bitflyer.jp',
                'doc': 'https://bitflyer.jp/API',
            },
            'api': {
                'public': {
                    'get': [
                        'getmarkets/usa', // new (wip)
                        'getmarkets/eu',  // new (wip)
                        'getmarkets',     // or 'markets'
                        'getboard',       // ...
                        'getticker',
                        'getexecutions',
                        'gethealth',
                        'getchats',
                    ],
                },
                'private': {
                    'get': [
                        'getpermissions',
                        'getbalance',
                        'getcollateral',
                        'getcollateralaccounts',
                        'getaddresses',
                        'getcoinins',
                        'getcoinouts',
                        'getbankaccounts',
                        'getdeposits',
                        'getwithdrawals',
                        'getchildorders',
                        'getparentorders',
                        'getparentorder',
                        'getexecutions',
                        'getpositions',
                        'gettradingcommission',
                    ],
                    'post': [
                        'sendcoin',
                        'withdraw',
                        'sendchildorder',
                        'cancelchildorder',
                        'sendparentorder',
                        'cancelparentorder',
                        'cancelallchildorders',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.25 / 100,
                    'taker': 0.25 / 100,
                },
            },
        });
    }

    async fetchMarkets () {
        let jp_markets = await this.publicGetGetmarkets ();
        let us_markets = await this.publicGetGetmarketsUsa ();
        let eu_markets = await this.publicGetGetmarketsEu ();
        let markets = this.arrayConcat (jp_markets, us_markets);
        markets = this.arrayConcat (markets, eu_markets);
        let result = [];
        for (let p = 0; p < markets.length; p++) {
            let market = markets[p];
            let id = market['product_code'];
            let currencies = id.split ('_');
            let base = undefined;
            let quote = undefined;
            let symbol = id;
            let numCurrencies = currencies.length;
            if (numCurrencies === 1) {
                base = symbol.slice (0, 3);
                quote = symbol.slice (3, 6);
            } else if (numCurrencies === 2) {
                base = currencies[0];
                quote = currencies[1];
                symbol = base + '/' + quote;
            } else {
                base = currencies[1];
                quote = currencies[2];
            }
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'info': market,
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privateGetGetbalance ();
        let balances = {};
        for (let b = 0; b < response.length; b++) {
            let account = response[b];
            let currency = account['currency_code'];
            balances[currency] = account;
        }
        let result = { 'info': response };
        let currencies = Object.keys (this.currencies);
        for (let i = 0; i < currencies.length; i++) {
            let currency = currencies[i];
            let account = this.account ();
            if (currency in balances) {
                account['total'] = balances[currency]['amount'];
                account['free'] = balances[currency]['available'];
                account['used'] = account['total'] - account['free'];
            }
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let orderbook = await this.publicGetGetboard (this.extend ({
            'product_code': this.marketId (symbol),
        }, params));
        return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'size');
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let ticker = await this.publicGetGetticker (this.extend ({
            'product_code': this.marketId (symbol),
        }, params));
        let timestamp = this.parse8601 (ticker['timestamp']);
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': undefined,
            'low': undefined,
            'bid': parseFloat (ticker['best_bid']),
            'ask': parseFloat (ticker['best_ask']),
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': parseFloat (ticker['ltp']),
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': parseFloat (ticker['volume_by_product']),
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    parseTrade (trade, market = undefined) {
        let side = undefined;
        let order = undefined;
        if ('side' in trade)
            if (trade['side']) {
                side = trade['side'].toLowerCase ();
                let id = side + '_child_order_acceptance_id';
                if (id in trade)
                    order = trade[id];
            }
        let timestamp = this.parse8601 (trade['exec_date']);
        return {
            'id': trade['id'].toString (),
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'order': order,
            'type': undefined,
            'side': side,
            'price': trade['price'],
            'amount': trade['size'],
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetGetexecutions (this.extend ({
            'product_code': market['id'],
        }, params));
        return this.parseTrades (response, market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let order = {
            'product_code': this.marketId (symbol),
            'child_order_type': type.toUpperCase (),
            'side': side.toUpperCase (),
            'price': price,
            'size': amount,
        };
        let result = await this.privatePostSendchildorder (this.extend (order, params));
        return {
            'info': result,
            'id': result['child_order_acceptance_id'],
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        if (typeof symbol === 'undefined')
            throw new ExchangeError (this.id + ' cancelOrder() requires a symbol argument');
        await this.loadMarkets ();
        return await this.privatePostCancelchildorder (this.extend ({
            'product_code': this.marketId (symbol),
            'child_order_acceptance_id': id,
        }, params));
    }

    parseOrderStatus (status) {
        let statuses = {
            'ACTIVE': 'open',
            'COMPLETED': 'closed',
            'CANCELED': 'canceled',
            'EXPIRED': 'canceled',
            'REJECTED': 'canceled',
        };
        return (status in statuses) ? statuses[status] : status.toLowerCase ();
    }

    parseOrder (order, market = undefined) {
        let timestamp = this.parse8601 (order['child_order_date']);
        let amount = this.safeFloat (order, 'size');
        let remaining = this.safeFloat (order, 'outstanding_size');
        let filled = this.safeFloat (order, 'executed_size');
        let price = this.safeFloat (order, 'price');
        let cost = price * filled;
        return {
            'id': order['child_order_acceptance_id'],
            'info': order,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'status': this.parseOrderStatus(order['child_order_state']),
            'symbol': market,
            'type': order['child_order_type'].toLowerCase (),
            'side': order['side'].toLowerCase (),
            'price': price,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'fee': this.safeFloat (order, 'total_commission'),
        };
    }

    async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        if (typeof symbol === 'undefined')
            throw new ExchangeError (this.id + ' cancelOrder() requires a symbol argument');
        let count = 100;
        if(limit)
            count = limit;
        await this.loadMarkets ();
        let response = await this.privateGetGetchildorders (this.extend ({
            'product_code': this.marketId (symbol),
            'count': count,
        }, params));
        let orders = this.parseOrders (response, symbol, since, limit);
        if (symbol)
            orders = this.filterBy (orders, 'symbol', symbol);
        return orders;
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        if (typeof symbol === 'undefined')
            throw new ExchangeError (this.id + ' cancelOrder() requires a symbol argument');
        let orders = await this.fetchOrders (symbol);
        for(let i = 0; i < orders.length; i++) {
            let order = orders[i]
            if(order['id'] === id)
                return order;
        }
        throw new OrderNotFound (this.id + ' No matching order found: ' + id);
    }

    async withdraw (currency, amount, address, tag = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostWithdraw (this.extend ({
            'currency_code': currency,
            'amount': amount,
            // 'bank_account_id': 1234,
        }, params));
        return {
            'info': response,
            'id': response['message_id'],
        };
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let request = '/' + this.version + '/';
        if (api === 'private')
            request += 'me/';
        request += path;
        if (method === 'GET') {
            if (Object.keys (params).length)
                request += '?' + this.urlencode (params);
        }
        let url = this.urls['api'] + request;
        if (api === 'private') {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            let auth = [ nonce, method, request ].join ('');
            if (Object.keys (params).length) {
                body = this.json (params);
                if (method !== 'GET')
                    auth += body;
            }
            headers = {
                'ACCESS-KEY': this.apiKey,
                'ACCESS-TIMESTAMP': nonce,
                'ACCESS-SIGN': this.hmac (this.encode (auth), this.encode (this.secret)),
                'Content-Type': 'application/json',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }
};
<|endoftext|>


<commit_before>/*
Gradebook from Names and Scores
I worked on this challenge [by myself, with:]
This challenge took me [#] hours.
You will work with the following two variables.  The first, students, holds the names of four students.
The second, scores, holds groups of test scores.  The relative positions of elements within the two
variables match (i.e., 'Joseph' is the first element in students; his scores are the first value in scores.).
Do not alter the students and scores code.
*/

var students = ["Joseph", "Susan", "William", "Elizabeth"]

var scores = [ [80, 70, 70, 100],
               [85, 80, 90, 90],
               [75, 70, 80, 75],
               [100, 90, 95, 85] ]

// __________________________________________
// Write your code below.

var gradebook = {}

for (var i = 0; i < scores.length; i++) {
  gradebook[students[i]] = {
      testScores: scores[i]
  }
}

gradebook.addScore = function(name, score) {
  gradebook[name].testScores.push(score);
}

gradebook.getAverage = function(name) {
  return average(gradebook[name].testScores)
}

var average = function(numbers) {
   var count = 0;
   var sum = 0;

   while (count < numbers.length) {
     sum += numbers[count];
     count++;
   }
  return sum / count;
}

/*
var average = function(numbers) {
  var sum = 0;

  for (var i = 0; i < numbers.length; i++) {
    sum += numbers[i];
  }
  return sum / numbers.length;
}
*/

// __________________________________________
// Refactored Solution

// __________________________________________
// Reflect

// __________________________________________
// Test Code:  Do not alter code below this line.

function assert(test, message, test_number) {
  if (!test) {
    console.log(test_number + "false");
    throw "ERROR: " + message;
  }
  console.log(test_number + "true");
  return true;
}

assert(
  (gradebook instanceof Object),
  "The value of gradebook should be an Object.\n",
  "1. "
)

assert(
  (gradebook["Elizabeth"] instanceof Object),
  "gradebook's Elizabeth property should be an object.",
  "2. "
)

assert(
  (gradebook.William.testScores === scores[2]),
  "William's testScores should equal the third element in scores.",
  "3. "
)

assert(
  (gradebook.addScore instanceof Function),
  "The value of gradebook's addScore property should be a Function.",
  "4. "
)

gradebook.addScore("Susan", 80)

assert(
  (gradebook.Susan.testScores.length === 5
   && gradebook.Susan.testScores[4] === 80),
  "Susan's testScores should have a new score of 80 added to the end.",
  "5. "
)

assert(
  (gradebook.getAverage instanceof Function),
  "The value of gradebook's getAverage property should be a Function.",
  "6. "
)

assert(
  (average instanceof Function),
  "The value of average should be a Function.\n",
  "7. "
)
assert(
  average([1, 2, 3]) === 2,
  "average should return the average of the elements in the array argument.\n",
  "8. "
)

assert(
  (gradebook.getAverage("Joseph") === 80),
  "gradebook's getAverage should return 80 if passed 'Joseph'.",
  "9. "
)<commit_msg>Complete gradebook exercise w/reflection<commit_after>/*
Gradebook from Names and Scores
I worked on this challenge [by myself, with:]
This challenge took me [#] hours.
You will work with the following two variables.  The first, students, holds the names of four students.
The second, scores, holds groups of test scores.  The relative positions of elements within the two
variables match (i.e., 'Joseph' is the first element in students; his scores are the first value in scores.).
Do not alter the students and scores code.
*/

var students = ["Joseph", "Susan", "William", "Elizabeth"]

var scores = [ [80, 70, 70, 100],
               [85, 80, 90, 90],
               [75, 70, 80, 75],
               [100, 90, 95, 85] ]

// __________________________________________
// Write your code below.
/*
var gradebook = {}

for (var i = 0; i < scores.length; i++) {
  gradebook[students[i]] = {
      testScores: scores[i]
  }
}

gradebook.addScore = function(name, score) {
  gradebook[name].testScores.push(score);
}

gradebook.getAverage = function(name) {
  return average(gradebook[name].testScores)
}

var average = function(numbers) {
   var count = 0;
   var sum = 0;

   while (count < numbers.length) {
     sum += numbers[count];
     count++;
   }
  return sum / count;
}
*/

// __________________________________________
// Refactored Solution

var gradebook = {}

for (var i = 0; i < scores.length; i++) {
  gradebook[students[i]] = {
      testScores: scores[i]
  }
}

gradebook.addScore = function(name, score) {
  gradebook[name].testScores.push(score);
}

gradebook.getAverage = function(name) {
  return average(gradebook[name].testScores)
}

var average = function(numbers) {
  var sum = 0;

  for (var i = 0; i < numbers.length; i++) {
    sum += numbers[i];
  }
  return sum / numbers.length;
}

// __________________________________________
// Reflect
/*
What did you learn about adding functions to objects?  When adding functions to objects, I learned that you need to call the object, followed by a period and the name of the function.  This pulls in the object as well as naming the function.

How did you iterate over nested arrays in JavaScript?  To iterate over nested arrays, you would use multiple for loops.

Were there any new methods you were able to incorporate? If so, what were they and how did they work?  We did not use any new methods, but did use the push and length method.  Another new thing I learned was adding properties to objects.

*/

// __________________________________________
// Test Code:  Do not alter code below this line.

function assert(test, message, test_number) {
  if (!test) {
    console.log(test_number + "false");
    throw "ERROR: " + message;
  }
  console.log(test_number + "true");
  return true;
}

assert(
  (gradebook instanceof Object),
  "The value of gradebook should be an Object.\n",
  "1. "
)

assert(
  (gradebook["Elizabeth"] instanceof Object),
  "gradebook's Elizabeth property should be an object.",
  "2. "
)

assert(
  (gradebook.William.testScores === scores[2]),
  "William's testScores should equal the third element in scores.",
  "3. "
)

assert(
  (gradebook.addScore instanceof Function),
  "The value of gradebook's addScore property should be a Function.",
  "4. "
)

gradebook.addScore("Susan", 80)

assert(
  (gradebook.Susan.testScores.length === 5
   && gradebook.Susan.testScores[4] === 80),
  "Susan's testScores should have a new score of 80 added to the end.",
  "5. "
)

assert(
  (gradebook.getAverage instanceof Function),
  "The value of gradebook's getAverage property should be a Function.",
  "6. "
)

assert(
  (average instanceof Function),
  "The value of average should be a Function.\n",
  "7. "
)
assert(
  average([1, 2, 3]) === 2,
  "average should return the average of the elements in the array argument.\n",
  "8. "
)

assert(
  (gradebook.getAverage("Joseph") === 80),
  "gradebook's getAverage should return 80 if passed 'Joseph'.",
  "9. "
)<|endoftext|>


<commit_before>function _pair(x, xs) {
	return [x, xs];
}

function _is_pair(x) {
	return x instanceof Array && x.length == 2;
}

function _head(xs) {
	return xs[0];
}

function _tail(xs) {
	return xs[1];
}

function _is_list(xs) {
	return xs && (_is_empty(xs) || (_tail(xs) !== undefined && _is_list(_tail(xs))));
}

function _is_in_list(x, xs) {
	if (_is_empty(xs)) return false;
	if (x === _head(xs)) return true;
	return _is_in_list(x, _tail(xs));
}

function _list() {
	var the_list = [];
	for (var i = arguments.length - 1; i >= 0; i--)
		the_list = _pair(arguments[i], the_list);
	return the_list;
}

function array_test(x) {
	if (this.Array.isArray === undefined) {
		return x instanceof Array;
	} else {
		return Array.isArray(x);
	}
}

function _is_empty(xs) {
	if (array_test(xs)) {
		if (xs.length === 0) {
			return true;
		} else if (xs.length === 2) {
			return false;
		} else {
			return false;
		}
	} else {
		return false;
	}
}

function is_number(xs) {
	return typeof xs == 'number';
}

function is_string(xs) {
	return typeof xs == 'string';
}

function _length(xs) {
	return (_is_empty(xs)) ? 0 : 1 + _length(_tail(xs));
}

function apply(f,xs) {
	var args = [];
	var len = _length(xs);
	for (var i=0; i < len; i++) {
		args[i] = _head(xs);
		xs = _tail(xs);
	}
	return f.apply(f,args);
}

function map(f) {
	if (_is_empty(arguments[1])) return [];
	else {
		var f_args = [];
		var map_args = [f];
		for (var i=1; i < arguments.length; i++) {
			f_args[i-1] = _head(arguments[i]);
			map_args[i] = _tail(arguments[i]);
		}
		return _pair(f.apply(f,f_args),map.apply(map,map_args));
	}
}
function runtime() {
	var d = new Date();
	return d.getTime();
}

function _now() {
	return new Date().getTime();
}

function _string_to_char_list(str) {
	if (typeof str !== 'string' && !(str instanceof String)) {
		return _list();
	}
	var char_list = _list();
	for (var i = str.length - 1; i >= 0; i--) {
		char_list = _pair(str.charAt(i), char_list);
	}
	return char_list;
}

function _char_code(char) {
	return char.charCodeAt(0);
}

/*  UI Output */
function _process_output(args) {
	//window.latestConsole.display(x);
	//x = x+'';
	//x = x.replace(/(?:\r\n|\r|\n)/g, '<br />');

	var output = '';

	for (var i = 0; i < args.length; i++) {
		var arg = args[i];
		var segment = '';
		if (arg && _is_list(arg)) {
			// print list
			segment += '[';
			while (!_is_empty(arg)) {
				// nested list
				if (_is_list(_head(arg))) {
					segment += (_process_output([_head(arg)]) + ', ');
				} else {
					segment += (_process_output([_head(arg)]) + ', ');
				}
				arg = _tail(arg);
			}
			// remove ', '
			if (segment.length !== 1) {
				segment = segment.slice(0, -2);
			}
			segment += ']';
		} else if (arg && arg.tag === 'function_value') {
			segment = apply(refer(arg, 'toString'),_list(),'?');
		} else {
			segment = arg;
		}
		output += ((output === '' ? '' : ' ') + segment);
	}
	return output;
}

function _put() {
	var output_value = _process_output(arguments);
	jqconsole.Write(output_value, 'console-default');
}

function _print() {
	var output_value = _process_output(arguments);
	jqconsole.Write(output_value + '\n', 'console-default');
}

function _input(message, value, show_in_console) {
	var input_value = prompt(message, value || '');
	if (show_in_console !== false) {
		jqconsole.Write(input_value + '\n', 'jqconsole-old-input');
	}
	return input_value;
	/*
	jqconsole.Input(function(input) {
		//jqconsole.Write('=> ' + input + '\n', 'console-arrow');
	});*/
}<commit_msg>Support printing pairs<commit_after>function _pair(x, xs) {
	return [x, xs];
}

function _is_pair(x) {
	return x instanceof Array && x.length == 2;
}

function _head(xs) {
	return xs[0];
}

function _tail(xs) {
	return xs[1];
}

function _is_list(xs) {
	return xs && (_is_empty(xs) || (_tail(xs) !== undefined && _is_list(_tail(xs))));
}

function _is_in_list(x, xs) {
	if (_is_empty(xs)) return false;
	if (x === _head(xs)) return true;
	return _is_in_list(x, _tail(xs));
}

function _list() {
	var the_list = [];
	for (var i = arguments.length - 1; i >= 0; i--)
		the_list = _pair(arguments[i], the_list);
	return the_list;
}

function array_test(x) {
	if (this.Array.isArray === undefined) {
		return x instanceof Array;
	} else {
		return Array.isArray(x);
	}
}

function _is_empty(xs) {
	if (array_test(xs)) {
		if (xs.length === 0) {
			return true;
		} else if (xs.length === 2) {
			return false;
		} else {
			return false;
		}
	} else {
		return false;
	}
}

function is_number(xs) {
	return typeof xs == 'number';
}

function is_string(xs) {
	return typeof xs == 'string';
}

function _length(xs) {
	return (_is_empty(xs)) ? 0 : 1 + _length(_tail(xs));
}

function apply(f,xs) {
	var args = [];
	var len = _length(xs);
	for (var i=0; i < len; i++) {
		args[i] = _head(xs);
		xs = _tail(xs);
	}
	return f.apply(f,args);
}

function map(f) {
	if (_is_empty(arguments[1])) return [];
	else {
		var f_args = [];
		var map_args = [f];
		for (var i=1; i < arguments.length; i++) {
			f_args[i-1] = _head(arguments[i]);
			map_args[i] = _tail(arguments[i]);
		}
		return _pair(f.apply(f,f_args),map.apply(map,map_args));
	}
}
function runtime() {
	var d = new Date();
	return d.getTime();
}

function _now() {
	return new Date().getTime();
}

function _string_to_char_list(str) {
	if (typeof str !== 'string' && !(str instanceof String)) {
		return _list();
	}
	var char_list = _list();
	for (var i = str.length - 1; i >= 0; i--) {
		char_list = _pair(str.charAt(i), char_list);
	}
	return char_list;
}

function _char_code(char) {
	return char.charCodeAt(0);
}

function _list_to_string(arg, segment) {
	segment += '[';
	while (!_is_empty(arg)) {
		// nested list
		segment += (_process_output([_head(arg)]) + ', ');
		arg = _tail(arg);
	}
	// remove ', '
	if (segment.length !== 1) {
		segment = segment.slice(0, -2);
	}
	segment += ']';
	return segment;
}

function _pair_to_string(arg) {
	let str = '';
	str += '(';
	str += _process_output([_head(arg)]);
	str += ', ';
	str += _process_output([_tail(arg)]);
	str += ')';
	return str;
}

/*  UI Output */
function _process_output(args) {
	let output = '';

	for (let i = 0; i < args.length; i++) {
		let arg = args[i];
		let segment = '';
		if (arg && _is_list(arg)) {
			segment += _list_to_string(arg, '');
		} else if (arg && _is_pair(arg)) {
			segment += _pair_to_string(arg);
		} else if (arg && arg.tag === 'function_value') {
			segment = apply(refer(arg, 'toString'),_list(),'?');
		} else {
			segment = arg;
		}
		output += ((output === '' ? '' : ' ') + segment);
	}
	return output;
}

function _put() {
	var output_value = _process_output(arguments);
	jqconsole.Write(output_value, 'console-default');
}

function _print() {
	var output_value = _process_output(arguments);
	jqconsole.Write(output_value + '\n', 'console-default');
}

function _input(message, value, show_in_console) {
	var input_value = prompt(message, value || '');
	if (show_in_console !== false) {
		jqconsole.Write(input_value + '\n', 'jqconsole-old-input');
	}
	return input_value;
	/*
	jqconsole.Input(function(input) {
		//jqconsole.Write('=> ' + input + '\n', 'console-arrow');
	});*/
}<|endoftext|>


<commit_before>/*global HTMLDocument, Window, console */
this.Costanza = (function() {
  "use strict";

  function defaultReporter(info, err) {
    /*jshint eqnull:true */
    console.error('Costanza error:'
          + '\n  type: ' + info.type
          + '\n  section: ' + info.section
          + (info.url ? '\n  url: ' + info.url : '')
          + (info.line != null ? '\n  line: ' + info.line : '')
          + ((info.stack && info.stack.indexOf(info.msg) < 0) ? '\n\n' + info.msg : ''),
          + '\n\n' + (info.stack || info.msg));
  }

  var reportCallback = defaultReporter,
      currentSection = 'global',
      _onError,
      _setTimeout,
      _setInterval;

  function init(_reportCallback, options) {
    reportCallback = _reportCallback || defaultReporter;

    if (!window.onerror || !window.onerror._costanza) {
      _onError = _onError || window.onerror;
      window.onerror = onErrorRoot;

      window.addEventListener('error', onError, true);
    }

    // Allow users to opt out of the native prototype augmentation.
    if (options && options.safeMode) {
      return;
    }

    if (!setTimeout._costanza) {
      _setTimeout = setTimeout;
      window.setTimeout = function(callback, duration) {
        return _setTimeout(bind(callback), duration);
      };
      setTimeout._costanza = true;
    }

    if (!setInterval._costanza) {
      _setInterval = setInterval;
      window.setInterval = function(callback, interval) {
        return _setInterval(bind(callback), interval);
      };
      setInterval._costanza = true;
    }

    if (window.Element && Element.prototype.addEventListener && !Element.prototype.addEventListener._costanza) {

      wrapListener(Element.prototype);
      if (window.HTMLDocument) {
        wrapListener(HTMLDocument.prototype);
      }
      if (window.Window) {
        wrapListener(Window.prototype);
      }
    }

    function wrapListener(proto) {
      // We must pair the native implementation with the proper proto object as ios7 will throw
      // if they are not.
      proto._addEventListener = proto.addEventListener;
      proto.addEventListener = function(type, callback, useCapture) {
        callback._section = callback._section || bind(callback);
        this._addEventListener(type, callback._section, useCapture);
      };
      proto.addEventListener._costanza = true;

      proto._removeEventListener = proto.removeEventListener;
      proto.removeEventListener = function(type, callback, useCapture) {
        this._removeEventListener(type, callback._section || callback, useCapture);
      };
      proto.removeEventListener._costanza = true;
    }
  }
  function cleanup() {
    function cleanupListener(proto) {
      proto.addEventListener = proto._addEventListener;
      proto.removeEventListener = proto._removeEventListener;
    }

    reportCallback = defaultReporter;
    if (setTimeout._costanza) {
      window.setTimeout = _setTimeout;
    }
    if (setInterval._costanza) {
      window.setInterval = _setInterval;
    }
    if (window.Element && Element.prototype.addEventListener && Element.prototype.addEventListener._costanza) {
      cleanupListener(Element.prototype);
      if (window.HTMLDocument) {
        cleanupListener(HTMLDocument.prototype);
      }
      if (window.Window) {
        cleanupListener(Window.prototype);
      }
    }

    if (window.onerror && window.onerror._costanza) {
      window.onerror = _onError;

      window.removeEventListener('error', onError, true);
    }
  }

  function bind(name, callback) {
    if (!callback) {
      callback = name;
      name = currentSection;
    }

    if (callback._costanza) {
      return callback;
    }

    var ret = function() {
      var priorSite = currentSection;

      try {
        currentSection = name;

        callback.apply(this, arguments);
      } catch (err) {
        reportCallback({
          type: 'javascript',
          section: currentSection,
          msg: err.message,
          stack: err.stack
        }, err);
      } finally {
        currentSection = priorSite;
      }
    };
    ret._costanza = true;
    return ret;
  }

  function onError(errorMsg, url, lineNumber, error) {
    var type = 'javascript';

    // Handle ErrorEvent if we receieve it
    if (errorMsg && errorMsg.message) {
      url = errorMsg.filename;
      lineNumber = errorMsg.lineno;
      error = errorMsg.error;
      errorMsg = errorMsg.message;
    }

    if (errorMsg === 'Script error.' && !url) {
      // Ignore untrackable external errors locally
      return;
    }

    // This is a real event handler vs. the onerror special case.
    // Since some browsers decide to treat window.onerror as the error event handler,
    // we have to be prepared for either case
    if (errorMsg && errorMsg.srcElement) {
      // Don't submit duplciate events (and if we weren't already tracking
      // it, it probably wasn't that important)
      if (errorMsg.defaultPrevented || errorMsg._costanzaHandled) {
        return;
      }

      errorMsg._costanzaHandled = true;
      url = url || errorMsg.srcElement.src || errorMsg.srcElement.href;
      type = errorMsg.srcElement.tagName.toLowerCase();
      errorMsg = 'load-failed';
    }

    // Error argument added to the spec. Adding support so we can catch this as it rolls out
    // http://html5.org/tools/web-apps-tracker?from=8085&to=8086
    reportCallback({
      section: currentSection,
      url: url,
      line: lineNumber,
      type: type,

      // Cast error message to string as it sometimes can come through with objects, particularly DOM objects
      // containing circular references.
      msg: errorMsg+'',
      stack: error && error.stack
    }, error);
  }

  function onErrorRoot(errorMsg, url, lineNumber, error) {
    _onError && _onError(errorMsg, url, lineNumber, error);
    onError(errorMsg, url, lineNumber, error);
  }
  onErrorRoot._costanza = true;

  return {
    init: init,
    cleanup: cleanup,
    current: function() {
      return currentSection;
    },
    emit: function(info, error) {
      reportCallback(info, error);
    },
    bind: bind,
    run: function(name, callback) {
      bind(name, callback)();
    },
    onError: onError
  };
})();
<commit_msg>Fix IE10 document listener binds<commit_after>/*global HTMLDocument, Window, console */
this.Costanza = (function() {
  "use strict";

  function defaultReporter(info, err) {
    /*jshint eqnull:true */
    console.error('Costanza error:'
          + '\n  type: ' + info.type
          + '\n  section: ' + info.section
          + (info.url ? '\n  url: ' + info.url : '')
          + (info.line != null ? '\n  line: ' + info.line : '')
          + ((info.stack && info.stack.indexOf(info.msg) < 0) ? '\n\n' + info.msg : ''),
          + '\n\n' + (info.stack || info.msg));
  }

  var reportCallback = defaultReporter,
      currentSection = 'global',
      _onError,
      _setTimeout,
      _setInterval;

  function init(_reportCallback, options) {
    reportCallback = _reportCallback || defaultReporter;

    if (!window.onerror || !window.onerror._costanza) {
      _onError = _onError || window.onerror;
      window.onerror = onErrorRoot;

      window.addEventListener('error', onError, true);
    }

    // Allow users to opt out of the native prototype augmentation.
    if (options && options.safeMode) {
      return;
    }

    if (!setTimeout._costanza) {
      _setTimeout = setTimeout;
      window.setTimeout = function(callback, duration) {
        return _setTimeout(bind(callback), duration);
      };
      setTimeout._costanza = true;
    }

    if (!setInterval._costanza) {
      _setInterval = setInterval;
      window.setInterval = function(callback, interval) {
        return _setInterval(bind(callback), interval);
      };
      setInterval._costanza = true;
    }

    if (window.Element && Element.prototype.addEventListener && !Element.prototype.addEventListener._costanza) {

      wrapListener(Element.prototype);
      if (window.HTMLDocument || window.Document) {
        // IE10: Document, others are HTMLDocument
        wrapListener((window.HTMLDocument || window.Document).prototype);
      }
      if (window.Window) {
        wrapListener(Window.prototype);
      }
    }

    function wrapListener(proto) {
      // We must pair the native implementation with the proper proto object as ios7 will throw
      // if they are not.
      proto._addEventListener = proto.addEventListener;
      proto.addEventListener = function(type, callback, useCapture) {
        callback._section = callback._section || bind(callback);
        this._addEventListener(type, callback._section, useCapture);
      };
      proto.addEventListener._costanza = true;

      proto._removeEventListener = proto.removeEventListener;
      proto.removeEventListener = function(type, callback, useCapture) {
        this._removeEventListener(type, callback._section || callback, useCapture);
      };
      proto.removeEventListener._costanza = true;
    }
  }
  function cleanup() {
    function cleanupListener(proto) {
      proto.addEventListener = proto._addEventListener;
      proto.removeEventListener = proto._removeEventListener;
    }

    reportCallback = defaultReporter;
    if (setTimeout._costanza) {
      window.setTimeout = _setTimeout;
    }
    if (setInterval._costanza) {
      window.setInterval = _setInterval;
    }
    if (window.Element && Element.prototype.addEventListener && Element.prototype.addEventListener._costanza) {
      cleanupListener(Element.prototype);
      if (window.HTMLDocument) {
        cleanupListener(HTMLDocument.prototype);
      }
      if (window.Window) {
        cleanupListener(Window.prototype);
      }
    }

    if (window.onerror && window.onerror._costanza) {
      window.onerror = _onError;

      window.removeEventListener('error', onError, true);
    }
  }

  function bind(name, callback) {
    if (!callback) {
      callback = name;
      name = currentSection;
    }

    if (callback._costanza) {
      return callback;
    }

    var ret = function() {
      var priorSite = currentSection;

      try {
        currentSection = name;

        callback.apply(this, arguments);
      } catch (err) {
        reportCallback({
          type: 'javascript',
          section: currentSection,
          msg: err.message,
          stack: err.stack
        }, err);
      } finally {
        currentSection = priorSite;
      }
    };
    ret._costanza = true;
    return ret;
  }

  function onError(errorMsg, url, lineNumber, error) {
    var type = 'javascript';

    // Handle ErrorEvent if we receieve it
    if (errorMsg && errorMsg.message) {
      url = errorMsg.filename;
      lineNumber = errorMsg.lineno;
      error = errorMsg.error;
      errorMsg = errorMsg.message;
    }

    if (errorMsg === 'Script error.' && !url) {
      // Ignore untrackable external errors locally
      return;
    }

    // This is a real event handler vs. the onerror special case.
    // Since some browsers decide to treat window.onerror as the error event handler,
    // we have to be prepared for either case
    if (errorMsg && errorMsg.srcElement) {
      // Don't submit duplciate events (and if we weren't already tracking
      // it, it probably wasn't that important)
      if (errorMsg.defaultPrevented || errorMsg._costanzaHandled) {
        return;
      }

      errorMsg._costanzaHandled = true;
      url = url || errorMsg.srcElement.src || errorMsg.srcElement.href;
      type = errorMsg.srcElement.tagName.toLowerCase();
      errorMsg = 'load-failed';
    }

    // Error argument added to the spec. Adding support so we can catch this as it rolls out
    // http://html5.org/tools/web-apps-tracker?from=8085&to=8086
    reportCallback({
      section: currentSection,
      url: url,
      line: lineNumber,
      type: type,

      // Cast error message to string as it sometimes can come through with objects, particularly DOM objects
      // containing circular references.
      msg: errorMsg+'',
      stack: error && error.stack
    }, error);
  }

  function onErrorRoot(errorMsg, url, lineNumber, error) {
    _onError && _onError(errorMsg, url, lineNumber, error);
    onError(errorMsg, url, lineNumber, error);
  }
  onErrorRoot._costanza = true;

  return {
    init: init,
    cleanup: cleanup,
    current: function() {
      return currentSection;
    },
    emit: function(info, error) {
      reportCallback(info, error);
    },
    bind: bind,
    run: function(name, callback) {
      bind(name, callback)();
    },
    onError: onError
  };
})();
<|endoftext|>


<commit_before>WDP.display = {};
WDP.posts = {};

WDP.displayViz = function(data){
	//height and width of svg
	var height = window.innerHeight;
	var width = window.innerWidth;
	var padding = 0;

	var viz = d3.select("#viz-wrapper")
					.append('svg')
					.attr('id', 'viz')
					.attr('height', height)
					.attr('width', width)
					.append('g');

	var rDomain = d3.extent(data, function(d){
        return d.amount;
    });
	var rScale = d3.scale.linear().domain(rDomain).range([1, 25]);

	var dots = viz.selectAll('g.dots').data(data).enter().append('g').attr('class', 'dots');
	dots.attr('transform', function(d){
		var x = Math.max(padding, Math.random() * width - padding);
		var y = Math.max(padding, Math.random() * height - padding);
		return "translate(" + x + ", " + y + ")";
	});

	dots.append('circle').attr('r', function(d){ return rScale(d.amount); });
	dots.append('text').text(function(d){return d.name;});

	/*
	//background rectangles are being created, but I cannot get them to show up for some reason
	viz.selectAll("text").each(function(d, i){
		var bbox = this.getBBox();
		console.log(bbox);
		var padding = 2;
		var rect = d3.select(document.createElement("rect"))
			.attr("x", bbox.x - padding)
		    .attr("y", bbox.y - padding)
		    .attr("width", bbox.width + (padding*2))
		    .attr("height", bbox.height + (padding*2))
		    .attr('class', 'word_label_background');
		this.parentNode.insertBefore(rect.node(), this.nextSibling);

	});
	*/
}

WDP.displaySearchViz = function(requestData){
	$.ajax({
		url: WDP.baseUrl + 'data',
		type: 'POST',
		dataType: 'html',
		data: requestData
	})

	.done(function(searchResults) {
		if(searchResults.match(/^This IP has been automatically blocked./i)){
			WDP.displayError("Sorry, but it appears that Craigslist has blocked this site from collecting data.");
			console.error(searchResults);
			return;
		}
		var categorySetFactory = new WDP.set.countedCategorySetFactory();
		var categorySet = categorySetFactory.makeCountedCategorySet(WDP.models.subCategoryTypeName);
		var set = new WDP.set.countedSet();
		var postLinks = [];
		$(searchResults).find('a.hdrlnk').each(function(index) {
			var resultsLink = $(this);
			var title = resultsLink.text();
			var link = resultsLink.attr("href");
			//don't add links to nearby cities
			if(!link.match(/^http:/)){
				postLinks.push(link);
			}
			title.split(" ").map(function(word) {
				set.add(word);
				categorySet.add(word);
			});
		});
		WDP.displayDataForSet(set, categorySet);
		WDP.displayPostBodies(postLinks, set, categorySet);
	})
	.fail(function(jqXHR, textStatus, error) {
		WDP.displayError("Sorry, could not connect to Craigslist.");
	});
}

WDP.displayPostBodies = function(postLinks, countedSet, categorySet){
	WDP.posts.total = postLinks.length;
	WDP.posts.done = 0;

	postLinks.map(function(postLink) {
		$.ajax({
			url: WDP.baseUrl + 'data/cl-postbody',
			type: 'GET',
			dataType: 'html',
			data: {link : postLink, city : WDP.models.currentCity}
		})
		.done(function(post) {
			var postBody = $(post).find('#postingbody').text();
			postBody.split(" ").map(function(word) {
				countedSet.add(word);
				categorySet.add(word);
			});
		})
		.fail(function() {
			console.log("Could not retrieve post body");
		}).always(function() {
			WDP.posts.done++;
			WDP.display.counter();
			if(WDP.posts.done >= WDP.posts.total){
				WDP.displayDataForSet(countedSet, categorySet);
			}
		});
	});

}

WDP.displayWordList = function(sortedCollection){
	var main_list = $('#main_list');
	sortedCollection.map(function(elem) {
		main_list.append("
" + elem.name + ' ('+ elem.amount + ")
");
	});
}

WDP.resetViz = function(){
    document.getElementById('viz-wrapper').innerHTML = '';
    document.getElementById('main_list').innerHTML = '';
    document.getElementById('category-bar-graph-wrapper').innerHTML = '';
}

WDP.displayDataForSet = function(countedSet, categorySet){
	var sortedCollection = countedSet.getSortedCollection();
	WDP.resetViz();
	if(sortedCollection.length === 0){
		WDP.displayError("Sorry, no results found.");
		return;
	}
	WDP.displayWordList(sortedCollection);
	WDP.displayViz(sortedCollection);
	WDP.display.categoryBarChart(categorySet);
}

WDP.display.counter = function(){
	document.getElementById('progress-counter').innerHTML = 'Results scanned: ' + WDP.posts.done + '/' + WDP.posts.total;
	if(WDP.posts.done === WDP.posts.total){
		document.getElementById('progress_bar').style.width = '0%';
	}
	//don't continually update for performance reasons
	else if(WDP.posts.done % 20 === 0){
		var done = WDP.posts.done;
		var total = WDP.posts.total;
		document.getElementById('progress_bar').style.width = done / total * 100 + '%';
	}
}

WDP.display.categoryBarChart = function(categorySet){
	var sortedCollection = categorySet.getSortedCollection();

	var xDomain = d3.extent(sortedCollection, function(d){
        return d.amount;
    });
	var xScale = d3.scale.linear().domain(xDomain).range([0, 1]);

	d3.select("#category-bar-graph-wrapper")
  		.selectAll("div")
    	.data(sortedCollection)
  		.enter().append("div")
  		.attr('class', 'bar_chart_bar')
    	.style("width", function(d) { return xScale(d.amount) * 100 + "%"; })
    	.text(function(d) { return d.name + ' (' + d.amount + ')'; });
}

<commit_msg>refactor to create a more general get cl page data so it can be reused<commit_after>WDP.display = {};
WDP.posts = {};
WDP.detailViz = {}; //toplevel container for city detail viz

WDP.displayViz = function(data){
	//height and width of svg
	var height = window.innerHeight;
	var width = window.innerWidth;
	var padding = 0;

	var viz = d3.select("#viz-wrapper")
					.append('svg')
					.attr('id', 'viz')
					.attr('height', height)
					.attr('width', width)
					.append('g');

	var rDomain = d3.extent(data, function(d){
        return d.amount;
    });
	var rScale = d3.scale.linear().domain(rDomain).range([1, 25]);

	var dots = viz.selectAll('g.dots').data(data).enter().append('g').attr('class', 'dots');
	dots.attr('transform', function(d){
		var x = Math.max(padding, Math.random() * width - padding);
		var y = Math.max(padding, Math.random() * height - padding);
		return "translate(" + x + ", " + y + ")";
	});

	dots.append('circle').attr('r', function(d){ return rScale(d.amount); });
	dots.append('text').text(function(d){return d.name;});

	/*
	//background rectangles are being created, but I cannot get them to show up for some reason
	viz.selectAll("text").each(function(d, i){
		var bbox = this.getBBox();
		console.log(bbox);
		var padding = 2;
		var rect = d3.select(document.createElement("rect"))
			.attr("x", bbox.x - padding)
		    .attr("y", bbox.y - padding)
		    .attr("width", bbox.width + (padding*2))
		    .attr("height", bbox.height + (padding*2))
		    .attr('class', 'word_label_background');
		this.parentNode.insertBefore(rect.node(), this.nextSibling);

	});
	*/
}
/*
* called to init detail viz when getCraigslist data finishes
*/
WDP.detailViz.init = function(searchResults){
	WDP.error.clear();
	var categorySetFactory = new WDP.set.countedCategorySetFactory();
	var categorySet = categorySetFactory.makeCountedCategorySet(WDP.models.subCategoryTypeName);
	var set = new WDP.set.countedSet();
	var postLinks = [];
	$(searchResults).find('a.hdrlnk').each(function(index) {
		var resultsLink = $(this);
		var title = resultsLink.text();
		var link = resultsLink.attr("href");
		//don't add links to nearby cities
		if(!link.match(/^http:/)){
			postLinks.push(link);
		}
		title.split(" ").map(function(word) {
			set.add(word);
			categorySet.add(word);
		});
	});
	WDP.displayDataForSet(set, categorySet);
	WDP.displayPostBodies(postLinks, set, categorySet);
}

/*
* Request data is WDP.init.CraigslistRequestData object
* successFunc is called on success with no errors, takes parameter of the data returned, optional parameter of original requestData
*/
WDP.getCLPage = function(requestData, successFunc){
	$.ajax({
		url: WDP.baseUrl + 'data',
		type: 'POST',
		dataType: 'html',
		data: requestData
	})

	.done(function(searchResults) {
		if(searchResults.match(/^This IP has been automatically blocked./i)){
			WDP.error.display("Sorry, but it appears that Craigslist has blocked this site from collecting data.");
			console.error(searchResults);
			return;
		}
		successFunc(searchResults, requestData);
	})
	.fail(function(jqXHR, textStatus, error) {
		WDP.error.display("Sorry, could not connect to Craigslist.");
	});
}

WDP.displayPostBodies = function(postLinks, countedSet, categorySet){
	WDP.posts.total = postLinks.length;
	WDP.posts.done = 0;

	postLinks.map(function(postLink) {
		$.ajax({
			url: WDP.baseUrl + 'data/cl-postbody',
			type: 'GET',
			dataType: 'html',
			data: {link : postLink, city : WDP.models.currentCity}
		})
		.done(function(post) {
			var postBody = $(post).find('#postingbody').text();
			postBody.split(" ").map(function(word) {
				countedSet.add(word);
				categorySet.add(word);
			});
		})
		.fail(function() {
			console.log("Could not retrieve post body");
		}).always(function() {
			WDP.posts.done++;
			WDP.display.counter();
			if(WDP.posts.done >= WDP.posts.total){
				WDP.displayDataForSet(countedSet, categorySet);
			}
		});
	});

}

WDP.displayWordList = function(sortedCollection){
	var main_list = $('#main_list');
	sortedCollection.map(function(elem) {
		main_list.append("
" + elem.name + ' ('+ elem.amount + ")
");
	});
}

WDP.resetViz = function(){
    document.getElementById('viz-wrapper').innerHTML = '';
    document.getElementById('main_list').innerHTML = '';
    document.getElementById('category-bar-graph-wrapper').innerHTML = '';
}

WDP.displayDataForSet = function(countedSet, categorySet){
	var sortedCollection = countedSet.getSortedCollection();
	WDP.resetViz();
	if(sortedCollection.length === 0){
		WDP.error.display("Sorry, no results found.");
		return;
	}
	WDP.displayWordList(sortedCollection);
	WDP.displayViz(sortedCollection);
	WDP.display.categoryBarChart(categorySet);
}

WDP.display.counter = function(){
	document.getElementById('progress-counter').innerHTML = 'Results scanned: ' + WDP.posts.done + '/' + WDP.posts.total;
	if(WDP.posts.done === WDP.posts.total){
		document.getElementById('progress_bar').style.width = '0%';
	}
	//don't continually update for performance reasons
	else if(WDP.posts.done % 20 === 0){
		var done = WDP.posts.done;
		var total = WDP.posts.total;
		document.getElementById('progress_bar').style.width = done / total * 100 + '%';
	}
}

WDP.display.categoryBarChart = function(categorySet){
	var sortedCollection = categorySet.getSortedCollection();
	var xDomain = d3.extent(sortedCollection, function(d){
        return d.amount;
    });
	var xScale = d3.scale.linear().domain(xDomain).range([0, 1]);

	d3.select("#category-bar-graph-wrapper")
  		.selectAll("div")
    	.data(sortedCollection)
  		.enter().append("div")
  		.attr('class', 'bar_chart_bar')
    	.style("width", function(d) { return xScale(d.amount) * 100 + "%"; })
    	.text(function(d) { return d.name + ' (' + d.amount + ')'; });
}

<|endoftext|>


<commit_before>/*
Testing too many changes stepwise:
 - look at borders while rotating: as expected.
 - add .bordertest before and after rotations.  As expected.
 - make boneyard with pip values. done.
 - use console to verify pip values.
 - set #23 in pickDominoes and call sumTips on it. done

 - set #36 next to #23
   - update tips: #23 leftTip, #36 rightTip, #36.addClass('anyTip')
   - call sumTips
 - set #46 next to #36, rotated, and do same tests

 - connect to string and remove draggable?
 - don't worry about all the grids, just the ones that are active
 because of a Tip.  If a piece is dropped but not connected, highlight
 it differently (blue).
 - on drop, snap to (virtual) grid
   - then check to see if location validates as an extension of the play (update)
   - or just a random piece dropped in space (highlight temporary outcast?)
 - while dragging, message new lots only
*/

var MESSAGE_PROPERTIES = {
    "pieceId" : "PIECEID",
    "newTop" : "NEWTOP",
    "newLeft" : "NEWLEFT"
};

var readyToPlay = false;
var redplayer = false;
var channel = (Math.round (Math.random()*100000)).toString();
var destination = "/topic/dominoes";

var playPoints, scorePoints;

function setUpMessaging() {
    // construct the WebSocket location
}

function setUpBoneyard() {
    // create the dominoes
    var dominoes1 = ['db', 'd6', 'd5', 'd4', 'd3', 'd2', 'd1',
                          'b6', 'b5', 'b4', 'b3', 'b2', 'b1',
                                '56', '46', '36', '26', '16',
                                      '45', '35', '25', '15',
                                            '34', '24', '14',
                                                  '23', '13',
                                                        '12', 'back'];
    var dominoes = [['db', '0', '0'],   ['d6', '6', '6'],   ['d5', '5', '5'],
        ['d4', '4', '4'],   ['d3', '3', '3'],   ['d2', '2', '2'],   ['d1', '1', '1'],
        ['b6', '0', '6'],   ['b5', '0', '5'],   ['b4', '0', '4'],   ['b3', '0', '3'],
        ['b2', '0', '2'],   ['b1', '0', '1'],   ['56', '5', '6'],   ['46', '4', '6'],
        ['36', '3', '6'],   ['26', '2', '6'],   ['16', '1', '6'],   ['45', '4', '5'],
        ['35', '3', '5'],   ['25', '2', '5'],   ['15', '1', '5'],   ['34', '3', '4'],
        ['24', '2', '4'],   ['14', '1', '4'],   ['23', '2', '3'],   ['13', '1', '3'],
        ['12', '1', '2'],   ['back', '0', '0'] ];

    for (i=0; i < dominoes.length; i++) {
        //
        var d = dominoes[i][0];

        var dburl = "https://dl.dropboxusercontent.com/u/78878172/domis/";
        var pic = "";
        // newd = "
" + pic + "
";
        var newd = "
" + "
";
        $('div#boneyard').append($(newd).addClass('domino'));
        var newDomi = $('div#'+d);
        // $('div#'+d).append($(pic));
        $(newDomi).append($(pic));
        $(newDomi).attr('lPips', dominoes[i][1]);
        $(newDomi).attr('rPips', dominoes[i][2]);
    }
}

function pickDominoes() {
    // seven to each player (just 2 players for now)
    var back;
    // while testing: just move back out of the way
    $back = $('#back');
    $back.css('top', (8*18).toString());
    $back.css('left', (4*18).toString());
    console.log($back.position());
    // while testing: pick an arbitrary start
    var d23;
    $d23 = $('#23');
    $d23.css('top', (4*18).toString());
    $d23.css('left', (4*18).toString());
    // mark the playable tips
    $d23.addClass('leftTip');
    $d23.addClass('rightTip');
    $d23.addClass('anyTip');
    console.log('sumTips before of #23 only:', sumTips());
}

function getOffsets(d1, d2) {
    var pos1 = d1.position();
    var pos2 = d2.position();
    return {
        'x' : parseInt(pos1.left, 10) - parseInt(pos2.left, 10),
        'y' : parseInt(pos1.top, 10) - parseInt(pos2.top, 10)
    };
}

function getRotation(d) {
    if (d.hasClass('r90')) {
        return 'r90';
    }
    if (d.hasClass('r180')) {
        return 'r180';
    }
    if (d.hasClass('r270')) {
        return 'r270';
    }
    return 'r0';
}

function nearNorthTip(tip, d) {
    var pdiff = getOffsets(tip, d),
        dRot = getRotation(d);
    if (pdiff.y<0) {
        // d is not north of this NorthTip
        // also true for turning corner
        return false;
    }
    if (Math.abs(pdiff.y) > 80) {
        // lost in space
        // also true for turning a corner
        return false;
    }
    if ((dRot == 'r90') || (dRot == 'r270')) {
        if (Math.abs(pdiff.x) > 10) {
            // d is not in this column
            return false;
        }
        // let's connect
        removeNorthTip(tip);
        addNorthTip(d);
        return true;
    } else {
        // turning a corner
        // console.log("trying to turn a corner?", pdiff, dRot, tip, d);
        if (Math.abs(pdiff.x) < 10) {
            // adding a EastTip
            removeNorthTip(tip);
            addEastTip(d);
            return true;
        } else if (pdiff.x < 40) {
            // adding a WestTip
            removeNorthTip(tip);
            addWestTip(d);
            return true;
        }
        return false;
    }
}

function nearEastTip(tip, d) {
    var pdiff = getOffsets(tip, d),
        dRot = getRotation(d);
    if (pdiff.x>0) {
        // d is not east of this EastTip
        // also true for turning corner
        return false;
    }
    if (Math.abs(pdiff.x) > 80) {
        // lost in space
        // also true for turning a corner
        return false;
    }
    if ((dRot == 'r0') || (dRot == 'r180')) {
        if (Math.abs(pdiff.y) > 10) {
            // d is not in this row
            return false;
        }
        // let's connect
        removeEastTip(tip);
        addEastTip(d);
        return true;
    } else {
        // turning a corner
        // console.log("trying to turn a corner?", pdiff, dRot, tip, d);
        if (Math.abs(pdiff.y) < 10) {
            // adding a SouthTip
            removeEastTip(tip);
            addSouthTip(d);
            return true;
        } else if (pdiff.y < 37) {
            // adding a NorthTip
            removeEastTip(tip);
            addNorthTip(d);
            return true;
        }
        return false;
    }
}

function removeNorthTip(tip) {
    var rot = getRotation(tip);
    if (rot == 'r90') {
        primary = 'leftTip';
        secondary = 'rightTip';
    }
    if (rot == 'r270') {
        primary = 'rightTip';
        secondary = 'leftTip';
    }
    // else raise error
    tip.removeClass(primary);
    tip.draggable({ disabled: true });
    if (!tip.hasClass(secondary)) {
        tip.removeClass('anyTip');
    }
}

function removeEastTip(tip) {
    var rot = getRotation(tip);
    if (rot == 'r0') {
        primary = 'rightTip';
        secondary = 'leftTip';
    }
    if (rot == 'r180') {
        primary = 'leftTip';
        secondary = 'rightTip';
    }
    // else raise error
    tip.removeClass(primary);
    tip.draggable({ disabled: true });
    if (!tip.hasClass(secondary)) {
        tip.removeClass('anyTip');
    }
}

function addWestTip(tip) {
    var rot = getRotation(tip);
    if (rot == 'r0') {
        tip.addClass('leftTip');
    }
    if (rot == 'r180') {
        tip.addClass('rightTip');
    }
    // else raise error
    tip.addClass('anyTip');
}

function addEastTip(tip) {
    var rot = getRotation(tip);
    if (rot == 'r0') {
        tip.addClass('rightTip');
    }
    if (rot == 'r180') {
        tip.addClass('leftTip');
    }
    // else raise error
    tip.addClass('anyTip');
}

function addSouthTip(tip) {
    var rot = getRotation(tip);
    if (rot == 'r90') {
        tip.addClass('rightTip');
    }
    if (rot == 'r270') {
        tip.addClass('leftTip');
    }
    // else raise error
    tip.addClass('anyTip');
}

function addNorthTip(tip) {
    var rot = getRotation(tip);
    if (rot == 'r90') {
        tip.addClass('leftTip');
    }
    if (rot == 'r270') {
        tip.addClass('rightTip');
    }
    // else raise error
    tip.addClass('anyTip');
}

function sumTips() {
    var score = 0;
    $('.anyTip').each(function(index, domino){
        $domino = $(domino);
        if ($domino.hasClass('leftTip')) {
            score += parseInt($domino.attr('lPips'), 10);
        }
        if ($domino.hasClass('rightTip')) {
            score += parseInt($domino.attr('rPips'), 10);
        }
    });
    return score;
}

nextRotation = {
    'r0'    : 'r90',
    'r90'   : 'r180',
    'r180'  : 'r270',
    'r270'  : 'r0'
};

function rotateMe(me) {
    var anyRotation = 'r90 r270 r180',
        mySpin, myNext;
    $me = $(me);
    mySpin = getRotation($me);
    myNext = nextRotation[mySpin];
    if (mySpin != 'r0') {
        $me.removeClass(anyRotation);
    }
    if (myNext != 'r0') {
        $me.addClass(myNext);
    }
}

$(document).ready(function() {
    setUpBoneyard();
    pickDominoes();
    $('.domino').each(function(index, domino){
        $domino = $(domino);
        // console.log(index, $domino);
        $domino.draggable({
            grid: [ 18, 18 ]
            // drag: function() { send all or each nth position},
        });
        $domino.dblclick(function() {
            // console.log("double click");
            rotateMe(this);
            // console.log("after rotate, position is: ", $(this).position());
            // Read the position
            // var pos = $(this).adjustedPosition();
            // console.log("adjusted pos is: ", pos);
            // $(this).css(pos);
        });
        $domino.mousedown(function() {
            // console.log("mouse down", $(this).position());
        });
        $domino.mouseup(function() {
            // console.log("mouse up", $(this).position());
            var $d = $(this);
            $('.anyTip').each(function(index, tip){
                $tip = $(tip);
                if ($tip.attr('id') == $d.attr('id')) {
                    return;
                }
                var rot = getRotation($tip);
                if ((rot == 'r0' && $tip.hasClass('rightTip')) ||
                    (rot == 'r180' && $tip.hasClass('leftTip'))) {
                    if (nearEastTip($tip, $d)) {
                        return;
                    }
                } else if ((rot == 'r90' && $tip.hasClass('leftTip')) ||
                    ((rot == 'r270') && $tip.hasClass('rightTip'))) {
                    if (nearNorthTip($tip, $d)) {
                        return;
                    }
                }
            });
        });
    });
});

<commit_msg>Rewrite partly working.<commit_after>/*
Testing too many changes stepwise:
 - look at borders while rotating: as expected.
 - add .bordertest before and after rotations.  As expected.
 - make boneyard with pip values. done.
 - use console to verify pip values.
 - set #23 in pickDominoes and call sumTips on it. done

 - set #36 next to #23
   - update tips: #23 leftTip, #36 rightTip, #36.addClass('anyTip')
   - call sumTips
 - set #46 next to #36, rotated, and do same tests

 - connect to string and remove draggable?
 - don't worry about all the grids, just the ones that are active
 because of a Tip.  If a piece is dropped but not connected, highlight
 it differently (blue).
 - on drop, snap to (virtual) grid
   - then check to see if location validates as an extension of the play (update)
   - or just a random piece dropped in space (highlight temporary outcast?)
 - while dragging, message new lots only
*/

var MESSAGE_PROPERTIES = {
    "pieceId" : "PIECEID",
    "newTop" : "NEWTOP",
    "newLeft" : "NEWLEFT"
};

var readyToPlay = false;
var redplayer = false;
var channel = (Math.round (Math.random()*100000)).toString();
var destination = "/topic/dominoes";

var playPoints, scorePoints;

function setUpMessaging() {
    // construct the WebSocket location
}

function setUpBoneyard() {
    // create the dominoes
    var dominoes1 = ['db', 'd6', 'd5', 'd4', 'd3', 'd2', 'd1',
                          'b6', 'b5', 'b4', 'b3', 'b2', 'b1',
                                '56', '46', '36', '26', '16',
                                      '45', '35', '25', '15',
                                            '34', '24', '14',
                                                  '23', '13',
                                                        '12', 'back'];
    var dominoes = [['db', '0', '0'],   ['d6', '6', '6'],   ['d5', '5', '5'],
        ['d4', '4', '4'],   ['d3', '3', '3'],   ['d2', '2', '2'],   ['d1', '1', '1'],
        ['b6', '0', '6'],   ['b5', '0', '5'],   ['b4', '0', '4'],   ['b3', '0', '3'],
        ['b2', '0', '2'],   ['b1', '0', '1'],   ['56', '5', '6'],   ['46', '4', '6'],
        ['36', '3', '6'],   ['26', '2', '6'],   ['16', '1', '6'],   ['45', '4', '5'],
        ['35', '3', '5'],   ['25', '2', '5'],   ['15', '1', '5'],   ['34', '3', '4'],
        ['24', '2', '4'],   ['14', '1', '4'],   ['23', '2', '3'],   ['13', '1', '3'],
        ['12', '1', '2'],   ['back', '0', '0'] ];

    for (i=0; i < dominoes.length; i++) {
        //
        var d = dominoes[i][0];

        var dburl = "https://dl.dropboxusercontent.com/u/78878172/domis/";
        var pic = "";
        // newd = "
" + pic + "
";
        var newd = "
" + "
";
        $('div#boneyard').append($(newd).addClass('domino'));
        var newDomi = $('div#'+d);
        // $('div#'+d).append($(pic));
        $(newDomi).append($(pic));
        $(newDomi).attr('lPips', dominoes[i][1]);
        $(newDomi).attr('rPips', dominoes[i][2]);
    }
}

function pickDominoes() {
    // seven to each player (just 2 players for now)
    var back;
    // while testing: just move back out of the way
    $back = $('#back');
    $back.css('top', (8*18).toString());
    $back.css('left', (4*18).toString());
    console.log($back.position());
    // while testing: pick an arbitrary start
    var d23;
    $d23 = $('#23');
    $d23.css('top', (4*18).toString());
    $d23.css('left', (4*18).toString());
    // mark the playable tips
    $d23.addClass('leftTip');
    $d23.addClass('rightTip');
    $d23.addClass('anyTip');
    console.log('sumTips before of #23 only:', sumTips());
}

function getOffsets(d1, d2) {
    var pos1 = d1.position();
    var pos2 = d2.position();
    return {
        'x' : parseInt(pos1.left, 10) - parseInt(pos2.left, 10),
        'y' : parseInt(pos1.top, 10) - parseInt(pos2.top, 10)
    };
}

function getRotation(d) {
    if (d.hasClass('r90')) {
        return 'r90';
    }
    if (d.hasClass('r180')) {
        return 'r180';
    }
    if (d.hasClass('r270')) {
        return 'r270';
    }
    return 'r0';
}

var cardinal = {
    'r0'    : {
        'rightTip'  : 'East',
        'leftTip'   : 'West'
    },
    'r90'   : {
        'rightTip'  : 'South',
        'leftTip'   : 'North'
    },
    'r180'    : {
        'rightTip'  : 'West',
        'leftTip'   : 'East'
    },
    'r270'   : {
        'rightTip'  : 'North',
        'leftTip'   : 'South'
    }};

var horizontal = {
    'r0'    : true,
    'r90'   : false,
    'r180'  : true,
    'r270'  : false
};

function nearTip(tip, rl, d) {
    var pdiff = getOffsets(tip, d),
        dRot = getRotation(d),
        tRot = getRotation(tip);
    var qual1 = {
        'East'  : function(tip, d) {
            // if d is east of tip, x is negative
            // and -80 < x < 0 is not too far east
            if ((pdiff.x < 0) && (pdiff.x > -82)) {
                if (horizontal[dRot]) {
                    // is it in the same row?
                    if (Math.abs(pdiff.y) < 10) {
                        return 'East';
                    }
                } else { // vertical
                    if (Math.abs(pdiff.y) < 10) {
                        // adding a SouthTip
                        return 'South';
                    } else if ((pdiff.y > 9) && (pdiff.y < 40)) {
                        // adding a NorthTip
                        return 'North';
                    }
                }
            }
            return 'None';
        },
        'South' : function(tip, d) {
            console.log('qualify South');
            return 'None';
        },
        'West'  : function(tip, d) {
            console.log('qualify West');
            return 'None';
        },
        'North' : function(tip, d) {
            console.log('qualify North');
            return 'None';
        }
    };
    // get the cardinal direction of the tip
    card = cardinal[tRot][rl];
    newCard = qual1[card](tip, d);
    if (newCard != 'None') {
        removeTip(tip, card);
        addTip(d, newCard);
        return true;
    }
    return false;
}

function nearNorthTip(tip, d) {
    var pdiff = getOffsets(tip, d),
        dRot = getRotation(d);
    if (pdiff.y<0) {
        // d is not north of this NorthTip
        // also true for turning corner
        return false;
    }
    if (Math.abs(pdiff.y) > 80) {
        // lost in space
        // also true for turning a corner
        return false;
    }
    if ((dRot == 'r90') || (dRot == 'r270')) {
        if (Math.abs(pdiff.x) > 10) {
            // d is not in this column
            return false;
        }
        // let's connect
        removeNorthTip(tip);
        addNorthTip(d);
        return true;
    } else {
        // turning a corner
        // console.log("trying to turn a corner?", pdiff, dRot, tip, d);
        if (Math.abs(pdiff.x) < 10) {
            // adding a EastTip
            removeNorthTip(tip);
            addEastTip(d);
            return true;
        } else if (pdiff.x > 0 && pdiff.x < 40) {
            // adding a WestTip
            removeNorthTip(tip);
            addWestTip(d);
            return true;
        }
        return false;
    }
}

function nearEastTip(tip, d) {
    var pdiff = getOffsets(tip, d),
        dRot = getRotation(d);
    if (pdiff.x>0) {
        // d is not east of this EastTip
        // also true for turning corner
        return false;
    }
    if (Math.abs(pdiff.x) > 80) {
        // lost in space
        // also true for turning a corner
        return false;
    }
    if ((dRot == 'r0') || (dRot == 'r180')) {
        if (Math.abs(pdiff.y) > 10) {
            // d is not in this row
            return false;
        }
        // let's connect
        removeEastTip(tip);
        addEastTip(d);
        return true;
    } else {
        // turning a corner
        // console.log("trying to turn a corner?", pdiff, dRot, tip, d);
        if (Math.abs(pdiff.y) < 10) {
            // adding a SouthTip
            removeEastTip(tip);
            addSouthTip(d);
            return true;
        } else if (pdiff.y < 37) {
            // adding a NorthTip
            removeEastTip(tip);
            addNorthTip(d);
            return true;
        }
        return false;
    }
}

var whichTips = {
    'r0'    : {
        'East'  : ['rightTip', 'leftTip'],
        'West'  : ['leftTip', 'rightTip']
    },
    'r90'   : {
        'North' : ['leftTip', 'rightTip'],
        'South' : ['rightTip', 'leftTip']
    },
    'r180'  : {
        'West'  : ['rightTip', 'leftTip'],
        'East'  : ['leftTip', 'rightTip']
    },
    'r270'  : {
        'South' : ['leftTip', 'rightTip'],
        'North' : ['rightTip', 'leftTip']
    }
}

function removeTip(tip, card) {
    var rot = getRotation(tip),
        wt = whichTips[rot][card];

    tip.removeClass(wt[0]);
    tip.draggable({ disabled: true });
    if (!tip.hasClass(wt[1])) {
        tip.removeClass('anyTip');
    }
}

function removeEastTip(tip) {
    var rot = getRotation(tip);
    if (rot == 'r0') {
        primary = 'rightTip';
        secondary = 'leftTip';
    }
    if (rot == 'r180') {
        primary = 'leftTip';
        secondary = 'rightTip';
    }
    // else raise error
    tip.removeClass(primary);
    tip.draggable({ disabled: true });
    if (!tip.hasClass(secondary)) {
        tip.removeClass('anyTip');
    }
}

function addTip(d, newCard) {
    var rot = getRotation(d),
        wt = whichTips[rot][newCard];
    d.addClass(wt[0]);
    d.addClass('anyTip');
}

function addEastTip(tip) {
    var rot = getRotation(tip);
    if (rot == 'r0') {
        tip.addClass('rightTip');
    }
    if (rot == 'r180') {
        tip.addClass('leftTip');
    }
    // else raise error
    tip.addClass('anyTip');
}

function addSouthTip(tip) {
    var rot = getRotation(tip);
    if (rot == 'r90') {
        tip.addClass('rightTip');
    }
    if (rot == 'r270') {
        tip.addClass('leftTip');
    }
    // else raise error
    tip.addClass('anyTip');
}

function addNorthTip(tip) {
    var rot = getRotation(tip);
    if (rot == 'r90') {
        tip.addClass('leftTip');
    }
    if (rot == 'r270') {
        tip.addClass('rightTip');
    }
    // else raise error
    tip.addClass('anyTip');
}

function sumTips() {
    var score = 0;
    $('.anyTip').each(function(index, domino){
        $domino = $(domino);
        if ($domino.hasClass('leftTip')) {
            score += parseInt($domino.attr('lPips'), 10);
        }
        if ($domino.hasClass('rightTip')) {
            score += parseInt($domino.attr('rPips'), 10);
        }
    });
    return score;
}

var nextRotation = {
    'r0'    : 'r90',
    'r90'   : 'r180',
    'r180'  : 'r270',
    'r270'  : 'r0'
};

function rotateMe(me) {
    var anyRotation = 'r90 r270 r180',
        mySpin, myNext;
    $me = $(me);
    mySpin = getRotation($me);
    myNext = nextRotation[mySpin];
    if (mySpin != 'r0') {
        $me.removeClass(anyRotation);
    }
    if (myNext != 'r0') {
        $me.addClass(myNext);
    }
}

$(document).ready(function() {
    setUpBoneyard();
    pickDominoes();
    $('.domino').each(function(index, domino){
        $domino = $(domino);
        // console.log(index, $domino);
        $domino.draggable({
            grid: [ 18, 18 ]
            // drag: function() { send all or each nth position},
        });
        $domino.dblclick(function() {
            // console.log("double click");
            rotateMe(this);
            // console.log("after rotate, position is: ", $(this).position());
            // Read the position
            // var pos = $(this).adjustedPosition();
            // console.log("adjusted pos is: ", pos);
            // $(this).css(pos);
        });
        $domino.mousedown(function() {
            // console.log("mouse down", $(this).position());
        });
        $domino.mouseup(function() {
            // console.log("mouse up", $(this).position());
            var $d = $(this);
            $('.anyTip').each(function(index, tip){
                $tip = $(tip);
                console.log("mouseup called", index);
                if ($tip.attr('id') == $d.attr('id')) {
                    return;
                }
                var placed = false;
                if ($tip.hasClass('rightTip')) {
                    placed = nearTip($tip, 'rightTip', $d);
                }
                if ($tip.hasClass('leftTip')) {
                    placed = nearTip($tip, 'leftTip', $d) || placed;
                }
                // if placed, return false to stop .each() loop
                return !placed;
            });
        });
    });
});

<|endoftext|>


<commit_before>'use strict';

function download(tabId, downloadRequest){
    new Download(downloadRequest, tabId).process();
}

function Download(downloadRequest, tabId){
    this.downloadRequest    = downloadRequest;
    this.tabId              = tabId;
    this.savePath           = '';
    this.filename           = '';
    this.basename           = '';
    this.prefix             = '';
}

Download.prototype = {
    process: function(){
        this.savePath = filenameTools.prepareSavePath(this.downloadRequest.path);
        this.prefix = filenameTools.preparePrefix(this.downloadRequest.filenamePrefix);

        if (this.downloadRequest.originalName) {
            this.filename = this.downloadRequest.originalName;
        } else {
            Object.assign(this, filenameTools.getFilename(this.downloadRequest.src));
        }

        if (this.filename) {
            this.download();
        } else {
            this.getHeadersAndDownload();
        }
    },

    getHeadersAndDownload: function(requestType = 'HEAD'){
        const xhr = new XMLHttpRequest();
        xhr.open(requestType, this.downloadRequest.src);
        xhr.onload = () => {
            if (requestType === 'HEAD' && [404, 405, 501].includes(xhr.status)) { // HEAD request method is not supported / allowed / implemented by server
                this.getHeadersAndDownload('GET');
            } else {
                this.setFilenameFromHeaders(xhr);
                this.download();
            }
        };
        xhr.onerror = () => {
            this.setFallbackFilename();
            this.download();
        };
        xhr.send();
    },

    setFilenameFromHeaders: function(xhr){
        const contentDisposition = xhr.getResponseHeader('Content-Disposition'),
            tmpFilename = contentDisposition && contentDisposition.match(/^.+filename\*?=(.{0,20}')?([^;]*);?$/i);

        if (tmpFilename) {
            this.filename = decodeURI(tmpFilename[2]).replace(/"/g, '');
        }
        if (!this.filename) {
            const contentType = xhr.getResponseHeader('Content-Type'),
                extension = contentType ? ('.' + contentType.match(/\w+\/(\w+)/)[1].replace('jpeg', 'jpg')) : '';

            this.filename = (this.basename || Date.now()) + extension
        }
    },

    setFallbackFilename: function(){
        const filenameTry = (this.downloadRequest.backupName || '').match(/[^\s]+\.(jpg|jpeg|png|gif|bmp|webm|mp4|ogg|mp3)/i);
        this.filename = filenameTry ? filenameTry[0] : this.downloadRequest.backupName;
    },

    download: function(){
        const finalFilename = filenameTools.prepareFilename(this.filename, this.prefix);
        chrome.downloads.download({
                url             : this.downloadRequest.src,
                filename        : this.savePath + finalFilename,
                saveAs          : this.downloadRequest.showSaveDialog,
                conflictAction  : 'uniquify'
            },
            downloadId => this.checkForDuplicate(finalFilename, downloadId)
        );
    },

    /*
    * If resulted filename is not the same as the filename, passed to downloads.download function,
    * then it was modified by 'uniquify' conflict action of WebExt API,
    * and user should be warned that he saved already existing file.
    */
    checkForDuplicate: function(originalFilename, downloadId){
        chrome.downloads.search({
            id: downloadId
        }, downloadItems => {
            if (!downloadItems[0] || !downloadItems[0].filename || downloadItems[0].filename.endsWith(originalFilename)) {return;}
            chrome.tabs.sendMessage(this.tabId, 'duplicate_warning');
        });
    },
};

const filenameTools = {
    /*
    * If the path is not empty then trims leading/trailing slashes/backslashes
    * and adds a single slash to the end for concating with filename.
    * Doesn't matter which slashes are used in save path, WebExt API recognizes both.
    */
    prepareSavePath: function(rawPath){
        return rawPath && (rawPath.replace(/^\\+|^\/+|\\+$|\/+$/, '') + '/');
    },

    preparePrefix: function(prefix){
        switch (prefix) {
            case '::date::' : {return new Date().toISOString().replace('T', '_').replace(/\..+/g, '').replace(/[^\d_]/g, '');}
            case '::time::' : {return new Date().getTime();}
            default         : {return prefix;}
        }
    },

    /*
    * Decodes url, cuts possible GET parameters, extracts filename from the url.
    * Usually filename is located at the end, after last "/" symbol, but sometimes
    * it might be somewhere in the middle between "/" symbols.
    * That's why it's important to extract filename manually by looking for the last sub-string
    * with pre-known extension located between "/" symbols.
    * WebExt API is incapable of extracting such filenames.
    * Cheers pineapple.
    */
    getFilename: function(originalUrl){
        const url = decodeURI(originalUrl).replace(/^.*https?:\/\/([^/]+)\/+/, '').split(/[?#]/)[0].replace(/:\w+$/, '').replace(/\/{2,}/, '/'),
            filenameTry = url.match(/^([^/]+\/)*([^/]+\.(jpg|jpeg|png|gif|bmp|webm|mp4|ogg|mp3))([\/][^.]+)?$/i);

        return filenameTry ?
            {filename: filenameTry[2]} :
            {basename: url.replace(/\/480$/, '').split('/').pop()}; // '/480' removal is a hack for tumblr videos
    },

    trimForbiddenWinChars: function(string){
        return string.replace(/[/\\:*?"<>|\x09]/g, '');
    },

    prepareFilename: function(filename, prefix){
        return this.trimForbiddenWinChars((prefix ? `${prefix}__` : '') + decodeURIComponent(filename));
    }
};
<commit_msg>less strict filename decoding<commit_after>'use strict';

function download(tabId, downloadRequest){
    new Download(downloadRequest, tabId).process();
}

function Download(downloadRequest, tabId){
    this.downloadRequest    = downloadRequest;
    this.tabId              = tabId;
    this.savePath           = '';
    this.filename           = '';
    this.basename           = '';
    this.prefix             = '';
}

Download.prototype = {
    process: function(){
        this.savePath = filenameTools.prepareSavePath(this.downloadRequest.path);
        this.prefix = filenameTools.preparePrefix(this.downloadRequest.filenamePrefix);

        if (this.downloadRequest.originalName) {
            this.filename = this.downloadRequest.originalName;
        } else {
            Object.assign(this, filenameTools.getFilename(this.downloadRequest.src));
        }

        if (this.filename) {
            this.download();
        } else {
            this.getHeadersAndDownload();
        }
    },

    getHeadersAndDownload: function(requestType = 'HEAD'){
        const xhr = new XMLHttpRequest();
        xhr.open(requestType, this.downloadRequest.src);
        xhr.onload = () => {
            if (requestType === 'HEAD' && [404, 405, 501].includes(xhr.status)) { // HEAD request method is not supported / allowed / implemented by server
                this.getHeadersAndDownload('GET');
            } else {
                this.setFilenameFromHeaders(xhr);
                this.download();
            }
        };
        xhr.onerror = () => {
            this.setFallbackFilename();
            this.download();
        };
        xhr.send();
    },

    setFilenameFromHeaders: function(xhr){
        const contentDisposition = xhr.getResponseHeader('Content-Disposition'),
            tmpFilename = contentDisposition && contentDisposition.match(/^.+filename\*?=(.{0,20}')?([^;]*);?$/i);

        if (tmpFilename) {
            this.filename = decodeURI(tmpFilename[2]).replace(/"/g, '');
        }
        if (!this.filename) {
            const contentType = xhr.getResponseHeader('Content-Type'),
                extension = contentType ? ('.' + contentType.match(/\w+\/(\w+)/)[1].replace('jpeg', 'jpg')) : '';

            this.filename = (this.basename || Date.now()) + extension
        }
    },

    setFallbackFilename: function(){
        const filenameTry = (this.downloadRequest.backupName || '').match(/[^\s]+\.(jpg|jpeg|png|gif|bmp|webm|mp4|ogg|mp3)/i);
        this.filename = filenameTry ? filenameTry[0] : this.downloadRequest.backupName;
    },

    download: function(){
        const finalFilename = filenameTools.prepareFilename(this.filename, this.prefix);
        chrome.downloads.download({
                url             : this.downloadRequest.src,
                filename        : this.savePath + finalFilename,
                saveAs          : this.downloadRequest.showSaveDialog,
                conflictAction  : 'uniquify'
            },
            downloadId => this.checkForDuplicate(finalFilename, downloadId)
        );
    },

    /*
    * If resulted filename is not the same as the filename, passed to downloads.download function,
    * then it was modified by 'uniquify' conflict action of WebExt API,
    * and user should be warned that he saved already existing file.
    */
    checkForDuplicate: function(originalFilename, downloadId){
        chrome.downloads.search({
            id: downloadId
        }, downloadItems => {
            if (!downloadItems[0] || !downloadItems[0].filename || downloadItems[0].filename.endsWith(originalFilename)) {return;}
            chrome.tabs.sendMessage(this.tabId, 'duplicate_warning');
        });
    },
};

const filenameTools = {
    /*
    * If the path is not empty then trims leading/trailing slashes/backslashes
    * and adds a single slash to the end for concating with filename.
    * Doesn't matter which slashes are used in save path, WebExt API recognizes both.
    */
    prepareSavePath: function(rawPath){
        return rawPath && (rawPath.replace(/^\\+|^\/+|\\+$|\/+$/, '') + '/');
    },

    preparePrefix: function(prefix){
        switch (prefix) {
            case '::date::' : {return new Date().toISOString().replace('T', '_').replace(/\..+/g, '').replace(/[^\d_]/g, '');}
            case '::time::' : {return new Date().getTime();}
            default         : {return prefix;}
        }
    },

    /*
    * Decodes url, cuts possible GET parameters, extracts filename from the url.
    * Usually filename is located at the end, after last "/" symbol, but sometimes
    * it might be somewhere in the middle between "/" symbols.
    * That's why it's important to extract filename manually by looking for the last sub-string
    * with pre-known extension located between "/" symbols.
    * WebExt API is incapable of extracting such filenames.
    * Cheers pineapple.
    */
    getFilename: function(originalUrl){
        const url = decodeURI(originalUrl).replace(/^.*https?:\/\/([^/]+)\/+/, '').split(/[?#]/)[0].replace(/:\w+$/, '').replace(/\/{2,}/, '/'),
            filenameTry = url.match(/^([^/]+\/)*([^/]+\.(jpg|jpeg|png|gif|bmp|webm|mp4|ogg|mp3))([\/][^.]+)?$/i);

        return filenameTry ?
            {filename: filenameTry[2]} :
            {basename: url.replace(/\/480$/, '').split('/').pop()}; // '/480' removal is a hack for tumblr videos
    },

    trimForbiddenWinChars: function(string){
        return string.replace(/[/\\:*?"<>|\x09]/g, '');
    },

    prepareFilename: function(filename, prefix){
        return this.trimForbiddenWinChars((prefix ? `${prefix}__` : '') + unescape(filename));
    }
};
<|endoftext|>