<commit_before>var _ = require('underscore');
var async = require('async');

var userConfig = require(process.env.HOME + '/.nodeplayer-config.js');
var defaultConfig = require('nodeplayer-defaults');
var config = _.defaults(userConfig, defaultConfig);

var player = {
    config: config,
    playedQueue: [],
    queue: [],
    plugins: {},
    backends: {}
}

// call hook function in all modules
// if any hooks return a truthy value, it is an error and we abort
// be very careful with calling hooks from within a hook, infinite loops are possible
var callHooks = function(hook, argv) {
    // _.find() used instead of _.each() because we want to break out as soon
    // as a hook returns a truthy value (used to indicate an error, e.g. in form
    // of a string)
    var err = null;

    _.find(player.plugins, function(plugin) {
        if(plugin[hook]) {
            err = plugin[hook].apply(null, argv);
            return err;
        }
    });

    return err;
};
player.callHooks = callHooks;

// returns number of hook functions attached to given hook
var numHooks = function(hook) {
    var cnt = 0;

    _.find(player.plugins, function(plugin) {
        if(plugin[hook]) {
            cnt++;
        }
    });

    return cnt;
};
player.numHooks = numHooks;

// start or resume playback of now playing song.
// if pos is undefined, playback continues (or starts from 0 if !playbackPosition)
player.songEndTimeout = null;
var startPlayback = function(pos) {
    var np = player.queue[0];
    if(pos)
        console.log('playing song: ' + np.songID + ', from pos: ' + pos);
    else
        console.log('playing song: ' + np.songID);

    player.playbackStart = new Date(); // song is playing while this is truthy

    // where did the song start playing from at playbackStart?
    if(!_.isUndefined(pos))
        player.playbackPosition = pos;
    else if(!player.playbackPosition)
        player.playbackPosition = 0;

    callHooks('onSongChange', [np]);

    var durationLeft = parseInt(np.duration) - player.playbackPosition + config.songDelayMs;
    if(player.songEndTimeout) {
        console.log('DEBUG: songEndTimeout was cleared');
        clearTimeout(player.songEndTimeout);
    }
    player.songEndTimeout = setTimeout(function() {
        console.log('end of song ' + np.songID);
        callHooks('onSongEnd', [np]);

        player.playedQueue.push(player.queue[0]);

        player.playbackPosition = null;
        player.playbackStart = null;
        player.queue[0] = null;
        player.songEndTimeout = null;
        onQueueModify();
    }, durationLeft);
};
player.startPlayback = startPlayback;

var pausePlayback = function() {
    // update position
    player.playbackPosition += new Date() - player.playbackStart;
    player.playbackStart = null;

    clearTimeout(player.songEndTimeout);
    player.songEndTimeout = null;
    callHooks('onSongPause', [player.nowPlaying]);
};
player.pausePlayback = pausePlayback;

var prepareError = function(song, err) {
    // remove all instances of this song
    for(var i = player.queue.length - 1; i >= 0; i--) {
        if(player.queue[i].songID === song.songID && player.queue[i].backendName === song.backendName) {
            if(!song.beingDeleted) {
                console.log('DEBUG: error! (' + err + ') removing song from queue: ' + song.songID);
                removeFromQueue(i);
            }
        }
    }

    callHooks('onSongPrepareError', [song]);
};

// TODO: get rid of the callback hell, use promises?
player.songsPreparing = {};
var prepareSong = function(song, asyncCallback) {
    if(!song) {
        console.log('DEBUG: prepareSong() without song');
        asyncCallback(true);
        return;
    }

    // create songsPreparing for current backend if one does not exist
    if(!player.songsPreparing[song.backendName])
        player.songsPreparing[song.backendName] = {};

    // don't run prepareSong() multiple times for the same song
    if(!player.songsPreparing[song.backendName][song.songID]) {
        //console.log('DEBUG: prepareSong() ' + song.songID);
        player.songsPreparing[song.backendName][song.songID] = song;

        song.cancelPrepare = player.backends[song.backendName].prepareSong(song.songID, function(dataSize, done) {
            /* progress callback
             * when this is called, new song data has been flushed to disk */

            // start playback if it hasn't been started yet
            if (player.queue[0]
                && player.queue[0].backendName === song.backendName
                && player.queue[0].songID === song.songID
                && !player.playbackStart
                && dataSize)
            {
                startPlayback();
            }

            if (done) {
                // mark song as prepared
                callHooks('onSongPrepared', [song]);

                // done preparing, can't cancel anymore
                delete(song.cancelPrepare);
                delete(player.songsPreparing[song.backendName][song.songID]);

                asyncCallback();
            }

            // tell plugins that new data is available for this song, and
            // whether the song is now fully written to disk or not.
            callHooks('onPrepareProgress', [song, dataSize, done]);

        }, function(err) {
            /* error callback */

            // don't let anything run cancelPrepare anymore
            delete(song.cancelPrepare);

            // abort preparing more songs
            asyncCallback(true);

            prepareError(song, err);
            delete(player.songsPreparing[song.backendName][song.songID]);
        });
    } else {
        // this song is already preparing, so don't yet prepare next song
        asyncCallback(true);
    }
};

// prepare now playing and queued songs for playback
var prepareSongs = function() {
    async.series([
        function(callback) {
            // prepare now-playing song if it exists and if not prepared
            if(player.queue[0]) {
                prepareSong(player.queue[0], callback);
            } else {
                callback(true);
            }
        },
        function(callback) {
            // prepare next song in queue if it exists and if not prepared
            if(player.queue[1]) {
                prepareSong(player.queue[1], callback);
            } else {
                callback(true);
            }
        }
    ]);
};
player.prepareSongs = prepareSongs;

// to be called whenever the queue has been modified
// this function will:
// - play back the first song in the queue if no song is playing
// - call prepareSongs()
var onQueueModify = function() {
    if(!player.queue.length) {
        callHooks('onEndOfQueue');
        console.log('end of queue, waiting for more songs');
        return;
    }

    // set next song as now playing, moves current into playedQueue
    if(!player.queue[0])
        player.playedQueue.push(player.queue.shift());

    prepareSongs();
    callHooks('onQueueModify', [player.queue]);
};
player.onQueueModify = onQueueModify;

// find song from queue
var searchQueue = function(backendName, songID) {
    for(var i = 0; i < player.queue.length; i++) {
        if(player.queue[i].songID === songID
                && player.queue[i].backendName === backendName)
            return player.queue[i];
    }

    return null;
};
player.searchQueue = searchQueue;

// make a search query to backends
var searchBackends = function(query, callback) {
    var resultCnt = 0;
    var allResults = {};

    _.each(player.backends, function(backend) {
        backend.search(query, function(results) {
            resultCnt++;

            // make a temporary copy of songlist, clear songlist, check
            // each song and add them again if they are ok
            var tempSongs = _.clone(results.songs);
            allResults[backend.name] = results;
            allResults[backend.name].songs = {};

            _.each(tempSongs, function(song) {
                var err = player.callHooks('preAddSearchResult', [player, song]);
                if(!err)
                    allResults[backend.name].songs[song.songID] = song;
            });

            // got results from all services?
            if(resultCnt >= Object.keys(player.backends).length)
                callback(allResults);
        }, function(err) {
            resultCnt++;
            console.log(err);

            // got results from all services?
            if(resultCnt >= Object.keys(player.backends).length)
                callback(allResults);
        });
    });
};

// get rid of song in either queue (negative signifies playedQueue)
// cnt can be left out for deleting only one song
var removeFromQueue = function(pos, cnt) {
    var retval;
    if(!cnt)
        cnt = 1;

    pos = parseInt(pos);
    callHooks('preSongsRemoved', [pos, cnt]);

    // remove songs from played queue
    if(pos < 0)
        retval = player.playedQueue.splice(player.playedQueue.length + pos, cnt);

    // remove songs from queue
    if(pos + cnt > 0) {
        if(player.queue.length) {
            // stop preparing songs we are about to remove
            // we want to limit this to player.queue.length if cnt is very large
            for(var i = 0; i < Math.min(player.queue.length, pos + cnt); i++) {
                var song = player.queue[i];

                // signal prepareError function not to run removeFromQueue again
                song.beingDeleted = true;
                if(song.cancelPrepare) {
                    song.cancelPrepare('song deleted');
                    delete(song.cancelPrepare);
                }
            }

            if(pos >= 0) {
                retval = player.queue.splice(pos, cnt);
            } else {
                // pos is negative: removed some from played queue, continue removing from zero
                retval = player.queue.splice(0, cnt + pos);
            }

            if(pos <= 0) {
                // now playing was deleted
                player.playbackPosition = null;
                player.playbackStart = null;
                clearTimeout(player.songEndTimeout);
                player.songEndTimeout = null;
            }
        }
    }

    onQueueModify();
    callHooks('postSongsRemoved', [pos, cnt]);
    return retval;
};
player.removeFromQueue = removeFromQueue;

// add songs to the queue, at optional position
var addToQueue = function(songs, pos) {
    if(!pos || pos < 0)
        pos = player.queue.length;
    pos = Math.min(pos, player.queue.length)

    callHooks('preSongsQueued', [songs, pos]);
    _.each(songs, function(song) {
        //console.log('DEBUG: addToQueue(): ' + song.songID);
        // check that required fields are provided
        if(!song.title || !song.songID || !song.backendName || !song.duration) {
            console.log('required song fields not provided: ' + song.songID);
            return 'required song fields not provided';
        }

        var err = callHooks('preSongQueued', [song]);
        if(err) {
            console.log('ERROR: not adding song to queue: ' + err);
        } else {
            song.timeAdded = new Date().getTime();

            player.queue.splice(pos++, 0, song);
            console.log('added song to queue: ' + song.songID);
            callHooks('postSongQueued', [song]);
        }
    })

    callHooks('sortQueue');
    onQueueModify();
    callHooks('postSongsQueued', [songs]);
};
player.addToQueue = addToQueue;

var shuffleQueue = function() {
    // don't change now playing
    var temp = player.queue.shift();
    player.queue = _.shuffle(player.queue);
    player.queue.unshift(temp);

    player.onQueueModify();
};
player.shuffleQueue = shuffleQueue;

// cnt can be negative to go back or zero to restart current song
var skipSongs = function(cnt) {
    player.npIsPlaying = false;

    for(var i = 0; i < Math.abs(cnt); i++) {
        if(cnt > 0) {
            if(player.queue[0])
                player.playedQueue.push(player.queue[0]);

            player.queue.shift();
        } else if(cnt < 0) {
            if(player.playedQueue.length)
                player.queue.unshift(player.playedQueue.pop());
        }

        // ran out of songs while skipping, stop
        if(!player.queue[0])
            break;
    }

    player.playbackPosition = null;
    player.playbackStart = null;
    clearTimeout(player.songEndTimeout);
    player.songEndTimeout = null;
    player.onQueueModify();
};

// init plugins
async.each(config.plugins, function(pluginName, callback) {
    // TODO: put plugin modules into npm
    // must implement .init, can implement hooks
    var plugin = require('./plugins/' + pluginName);

    plugin.init(player, function(err) {
        if(!err) {
            player.plugins[pluginName] = plugin;
            console.log('plugin ' + pluginName + ' initialized');
            callHooks('onPluginInitialized', [plugin]);
        } else {
            console.log('error in ' + pluginName + ': ' + err);
            callHooks('onPluginInitError', [plugin]);
        }
        callback(err);
    });
}, function(err) {
    callHooks('onPluginsInitialized');
});

// init backends
async.each(config.backends, function(backendName, callback) {
    var backend = require('nodeplayer-' + backendName);

    backend.init(player, function(err) {
        if(!err) {
            player.backends[backendName] = backend;
            console.log('backend ' + backendName + ' initialized');
            callHooks('onBackendInitialized', [backend]);
        } else {
            console.log('error in ' + backendName + ': ' + err);
            callHooks('onBackendInitError', [backend]);
        }
        callback(err);
    });
}, function(err) {
    callHooks('onBackendsInitialized');
});

process.on('uncaughtException', function (err) {
    console.error(err.stack);
    console.log("ERROR! Node not exiting.");
});

<commit_msg>oops forgot these<commit_after>var _ = require('underscore');
var async = require('async');

var userConfig = require(process.env.HOME + '/.nodeplayer-config.js');
var defaultConfig = require('nodeplayer-defaults');
var config = _.defaults(userConfig, defaultConfig);

var player = {
    config: config,
    playedQueue: [],
    queue: [],
    plugins: {},
    backends: {}
}

// call hook function in all modules
// if any hooks return a truthy value, it is an error and we abort
// be very careful with calling hooks from within a hook, infinite loops are possible
var callHooks = function(hook, argv) {
    // _.find() used instead of _.each() because we want to break out as soon
    // as a hook returns a truthy value (used to indicate an error, e.g. in form
    // of a string)
    var err = null;

    _.find(player.plugins, function(plugin) {
        if(plugin[hook]) {
            err = plugin[hook].apply(null, argv);
            return err;
        }
    });

    return err;
};
player.callHooks = callHooks;

// returns number of hook functions attached to given hook
var numHooks = function(hook) {
    var cnt = 0;

    _.find(player.plugins, function(plugin) {
        if(plugin[hook]) {
            cnt++;
        }
    });

    return cnt;
};
player.numHooks = numHooks;

// start or resume playback of now playing song.
// if pos is undefined, playback continues (or starts from 0 if !playbackPosition)
player.songEndTimeout = null;
var startPlayback = function(pos) {
    var np = player.queue[0];
    if(pos)
        console.log('playing song: ' + np.songID + ', from pos: ' + pos);
    else
        console.log('playing song: ' + np.songID);

    player.playbackStart = new Date(); // song is playing while this is truthy

    // where did the song start playing from at playbackStart?
    if(!_.isUndefined(pos))
        player.playbackPosition = pos;
    else if(!player.playbackPosition)
        player.playbackPosition = 0;

    callHooks('onSongChange', [np]);

    var durationLeft = parseInt(np.duration) - player.playbackPosition + config.songDelayMs;
    if(player.songEndTimeout) {
        console.log('DEBUG: songEndTimeout was cleared');
        clearTimeout(player.songEndTimeout);
    }
    player.songEndTimeout = setTimeout(function() {
        console.log('end of song ' + np.songID);
        callHooks('onSongEnd', [np]);

        player.playedQueue.push(player.queue[0]);

        player.playbackPosition = null;
        player.playbackStart = null;
        player.queue[0] = null;
        player.songEndTimeout = null;
        onQueueModify();
    }, durationLeft);
};
player.startPlayback = startPlayback;

var pausePlayback = function() {
    // update position
    player.playbackPosition += new Date() - player.playbackStart;
    player.playbackStart = null;

    clearTimeout(player.songEndTimeout);
    player.songEndTimeout = null;
    callHooks('onSongPause', [player.nowPlaying]);
};
player.pausePlayback = pausePlayback;

var prepareError = function(song, err) {
    // remove all instances of this song
    for(var i = player.queue.length - 1; i >= 0; i--) {
        if(player.queue[i].songID === song.songID && player.queue[i].backendName === song.backendName) {
            if(!song.beingDeleted) {
                console.log('DEBUG: error! (' + err + ') removing song from queue: ' + song.songID);
                removeFromQueue(i);
            }
        }
    }

    callHooks('onSongPrepareError', [song]);
};

// TODO: get rid of the callback hell, use promises?
player.songsPreparing = {};
var prepareSong = function(song, asyncCallback) {
    if(!song) {
        console.log('DEBUG: prepareSong() without song');
        asyncCallback(true);
        return;
    }

    // create songsPreparing for current backend if one does not exist
    if(!player.songsPreparing[song.backendName])
        player.songsPreparing[song.backendName] = {};

    // don't run prepareSong() multiple times for the same song
    if(!player.songsPreparing[song.backendName][song.songID]) {
        //console.log('DEBUG: prepareSong() ' + song.songID);
        player.songsPreparing[song.backendName][song.songID] = song;

        song.cancelPrepare = player.backends[song.backendName].prepareSong(song.songID, function(dataSize, done) {
            /* progress callback
             * when this is called, new song data has been flushed to disk */

            // start playback if it hasn't been started yet
            if (player.queue[0]
                && player.queue[0].backendName === song.backendName
                && player.queue[0].songID === song.songID
                && !player.playbackStart
                && dataSize)
            {
                startPlayback();
            }

            if (done) {
                // mark song as prepared
                callHooks('onSongPrepared', [song]);

                // done preparing, can't cancel anymore
                delete(song.cancelPrepare);
                delete(player.songsPreparing[song.backendName][song.songID]);

                asyncCallback();
            }

            // tell plugins that new data is available for this song, and
            // whether the song is now fully written to disk or not.
            callHooks('onPrepareProgress', [song, dataSize, done]);

        }, function(err) {
            /* error callback */

            // don't let anything run cancelPrepare anymore
            delete(song.cancelPrepare);

            // abort preparing more songs
            asyncCallback(true);

            prepareError(song, err);
            delete(player.songsPreparing[song.backendName][song.songID]);
        });
    } else {
        // this song is already preparing, so don't yet prepare next song
        asyncCallback(true);
    }
};

// prepare now playing and queued songs for playback
var prepareSongs = function() {
    async.series([
        function(callback) {
            // prepare now-playing song if it exists and if not prepared
            if(player.queue[0]) {
                prepareSong(player.queue[0], callback);
            } else {
                callback(true);
            }
        },
        function(callback) {
            // prepare next song in queue if it exists and if not prepared
            if(player.queue[1]) {
                prepareSong(player.queue[1], callback);
            } else {
                callback(true);
            }
        }
    ]);
};
player.prepareSongs = prepareSongs;

// to be called whenever the queue has been modified
// this function will:
// - play back the first song in the queue if no song is playing
// - call prepareSongs()
var onQueueModify = function() {
    if(!player.queue.length) {
        callHooks('onEndOfQueue');
        console.log('end of queue, waiting for more songs');
        return;
    }

    // set next song as now playing, moves current into playedQueue
    if(!player.queue[0])
        player.playedQueue.push(player.queue.shift());

    prepareSongs();
    callHooks('onQueueModify', [player.queue]);
};
player.onQueueModify = onQueueModify;

// find song from queue
var searchQueue = function(backendName, songID) {
    for(var i = 0; i < player.queue.length; i++) {
        if(player.queue[i].songID === songID
                && player.queue[i].backendName === backendName)
            return player.queue[i];
    }

    return null;
};
player.searchQueue = searchQueue;

// make a search query to backends
var searchBackends = function(query, callback) {
    var resultCnt = 0;
    var allResults = {};

    _.each(player.backends, function(backend) {
        backend.search(query, function(results) {
            resultCnt++;

            // make a temporary copy of songlist, clear songlist, check
            // each song and add them again if they are ok
            var tempSongs = _.clone(results.songs);
            allResults[backend.name] = results;
            allResults[backend.name].songs = {};

            _.each(tempSongs, function(song) {
                var err = player.callHooks('preAddSearchResult', [player, song]);
                if(!err)
                    allResults[backend.name].songs[song.songID] = song;
            });

            // got results from all services?
            if(resultCnt >= Object.keys(player.backends).length)
                callback(allResults);
        }, function(err) {
            resultCnt++;
            console.log(err);

            // got results from all services?
            if(resultCnt >= Object.keys(player.backends).length)
                callback(allResults);
        });
    });
};
player.searchBackends = searchBackends;

// get rid of song in either queue (negative signifies playedQueue)
// cnt can be left out for deleting only one song
var removeFromQueue = function(pos, cnt) {
    var retval;
    if(!cnt)
        cnt = 1;

    pos = parseInt(pos);
    callHooks('preSongsRemoved', [pos, cnt]);

    // remove songs from played queue
    if(pos < 0)
        retval = player.playedQueue.splice(player.playedQueue.length + pos, cnt);

    // remove songs from queue
    if(pos + cnt > 0) {
        if(player.queue.length) {
            // stop preparing songs we are about to remove
            // we want to limit this to player.queue.length if cnt is very large
            for(var i = 0; i < Math.min(player.queue.length, pos + cnt); i++) {
                var song = player.queue[i];

                // signal prepareError function not to run removeFromQueue again
                song.beingDeleted = true;
                if(song.cancelPrepare) {
                    song.cancelPrepare('song deleted');
                    delete(song.cancelPrepare);
                }
            }

            if(pos >= 0) {
                retval = player.queue.splice(pos, cnt);
            } else {
                // pos is negative: removed some from played queue, continue removing from zero
                retval = player.queue.splice(0, cnt + pos);
            }

            if(pos <= 0) {
                // now playing was deleted
                player.playbackPosition = null;
                player.playbackStart = null;
                clearTimeout(player.songEndTimeout);
                player.songEndTimeout = null;
            }
        }
    }

    onQueueModify();
    callHooks('postSongsRemoved', [pos, cnt]);
    return retval;
};
player.removeFromQueue = removeFromQueue;

// add songs to the queue, at optional position
var addToQueue = function(songs, pos) {
    if(!pos || pos < 0)
        pos = player.queue.length;
    pos = Math.min(pos, player.queue.length)

    callHooks('preSongsQueued', [songs, pos]);
    _.each(songs, function(song) {
        //console.log('DEBUG: addToQueue(): ' + song.songID);
        // check that required fields are provided
        if(!song.title || !song.songID || !song.backendName || !song.duration) {
            console.log('required song fields not provided: ' + song.songID);
            return 'required song fields not provided';
        }

        var err = callHooks('preSongQueued', [song]);
        if(err) {
            console.log('ERROR: not adding song to queue: ' + err);
        } else {
            song.timeAdded = new Date().getTime();

            player.queue.splice(pos++, 0, song);
            console.log('added song to queue: ' + song.songID);
            callHooks('postSongQueued', [song]);
        }
    })

    callHooks('sortQueue');
    onQueueModify();
    callHooks('postSongsQueued', [songs]);
};
player.addToQueue = addToQueue;

var shuffleQueue = function() {
    // don't change now playing
    var temp = player.queue.shift();
    player.queue = _.shuffle(player.queue);
    player.queue.unshift(temp);

    player.onQueueModify();
};
player.shuffleQueue = shuffleQueue;

// cnt can be negative to go back or zero to restart current song
var skipSongs = function(cnt) {
    player.npIsPlaying = false;

    for(var i = 0; i < Math.abs(cnt); i++) {
        if(cnt > 0) {
            if(player.queue[0])
                player.playedQueue.push(player.queue[0]);

            player.queue.shift();
        } else if(cnt < 0) {
            if(player.playedQueue.length)
                player.queue.unshift(player.playedQueue.pop());
        }

        // ran out of songs while skipping, stop
        if(!player.queue[0])
            break;
    }

    player.playbackPosition = null;
    player.playbackStart = null;
    clearTimeout(player.songEndTimeout);
    player.songEndTimeout = null;
    player.onQueueModify();
};
player.skipSongs = skipSongs;

// init plugins
async.each(config.plugins, function(pluginName, callback) {
    // TODO: put plugin modules into npm
    // must implement .init, can implement hooks
    var plugin = require('./plugins/' + pluginName);

    plugin.init(player, function(err) {
        if(!err) {
            player.plugins[pluginName] = plugin;
            console.log('plugin ' + pluginName + ' initialized');
            callHooks('onPluginInitialized', [plugin]);
        } else {
            console.log('error in ' + pluginName + ': ' + err);
            callHooks('onPluginInitError', [plugin]);
        }
        callback(err);
    });
}, function(err) {
    callHooks('onPluginsInitialized');
});

// init backends
async.each(config.backends, function(backendName, callback) {
    var backend = require('nodeplayer-' + backendName);

    backend.init(player, function(err) {
        if(!err) {
            player.backends[backendName] = backend;
            console.log('backend ' + backendName + ' initialized');
            callHooks('onBackendInitialized', [backend]);
        } else {
            console.log('error in ' + backendName + ': ' + err);
            callHooks('onBackendInitError', [backend]);
        }
        callback(err);
    });
}, function(err) {
    callHooks('onBackendsInitialized');
});

process.on('uncaughtException', function (err) {
    console.error(err.stack);
    console.log("ERROR! Node not exiting.");
});

<|endoftext|>


<commit_before>// PlaystateClass.js
function PlaystateClass() {
	"use strict";
	this.stateName = "play";

	this.listed = false;
	this.mapList = new LPMapList();

	this.tiled = false;
	this.tiledMap = new LPTiledMap();
	this.tiledMapOffsets = new LPTiledMapOffsets();
	this.tiledMapOffsets.curOffsetX = -1;
	this.tiledMapOffsets.curOffsetY = -1;
	this.tiledMapOffsets.oldOffsetX = -1;
	this.tiledMapOffsets.oldOffsetY = -1;

	this.mapOffsetSet = false;
	this.initialized = false;
	this.completed = false;

	this.userInput = "";

	this.player = new LPPlayerCharacter();
	this.playerVelocityXMod = 2;
	this.inertiaMod = 5;
	this.playerVelocityJumpMod = 4;
	this.gravityVelocityMod = 0.1;
	this.terminalVelocity = 20;

	this.playerPosLimitLeft = 192;
	this.playerPosLimitRight = 608;

	this.jumpSnd = new Audio("res/snd/jump_02.ogg");
	this.prevTime = new Date().getTime();
}
PlaystateClass.prototype = new GamestateClass();
PlaystateClass.prototype.constructor = PlaystateClass;

PlaystateClass.prototype.initialize = function () {
	"use strict";
	this.listed = false;
	this.mapList = new LPMapList();

	this.tiled = false;
	this.tiledMap = new LPTiledMap();

	this.tiledMapOffsets.curOffsetX = -1;
	this.tiledMapOffsets.curOffsetY = -1;
	this.tiledMapOffsets.oldOffsetX = -1;
	this.tiledMapOffsets.oldOffsetY = -1;

	this.mapOffsetSet = false;
	this.initialized = false;
	this.completed = false;

	this.userInput = "";

	this.newState = "";
}

PlaystateClass.prototype.update = function (keysDown) {
	"use strict";
	if (this.newState != "") {
		this.initialize();
	}

	var deltaTime = new Date().getTime() - this.prevTime;
	if (!this.initialized) {
		if (!this.listed) {
			this.mapList.loadMapList("maplist");
			this.listed = true;
		}

		if (this.listed && this.mapList.mapListLoaded && !this.tiled) {
			this.tiledMap.loadMap(this.mapList.mapList[this.mapList.currentMapIndex++]);
			this.tiled = true;
		}

		if (this.tiled && this.tiledMap.mapLoaded) {
			if (this.tiledMap.mapPXHeight < 600) {
				this.tiledMapOffsets.curOffsetY = (600 - this.tiledMap.mapPXHeight)/2;
//				this.mapOffsetY = (600 - this.tiledMap.mapPXHeight)/2;
			} else {
				this.tiledMapOffsets.curOffsetY = 0;
			}
			if (this.tiledMap.mapPXWidth < 800) {
				this.tiledMapOffsets.curOffsetX = (800 - this.tiledMap.mapPXWidth)/2;
//				this.mapOffsetX = (800 - this.tiledMap.mapPXWidth)/2;
			} else {
				this.tiledMapOffsets.curOffsetX = 0;
			}

			this.mapOffsetSet = true;
		}

		if (this.mapOffsetSet) {
			this.player.setPosition(this.tiledMap.startX + this.tiledMapOffsets.curOffsetX, this.tiledMap.startY + this.tiledMapOffsets.curOffsetY - 32);
			this.initialized = true;
		}
	} else {
		if (!this.completed) {
			var tempXVelocity = this.player.xVelocity;
			var tempYVelocity = this.player.yVelocity;
			if (getConstant("A") in keysDown || getConstant("LEFT") in keysDown) {
				tempXVelocity -= this.playerVelocityXMod;
				if (tempXVelocity < -this.player.xMaxVelocity) {
					tempXVelocity = -this.player.xMaxVelocity;
				}
			}
			if (getConstant("D") in keysDown || getConstant("RIGHT") in keysDown) {
				tempXVelocity += this.playerVelocityXMod;
				if (tempXVelocity > this.player.xMaxVelocity) {
					tempXVelocity = this.player.xMaxVelocity;
				}
			}

			if (getConstant("W") in keysDown || getConstant("UP") in keysDown) {
				if (!this.player.jumping) {
					this.jumpSnd.play();
					tempYVelocity = -this.playerVelocityJumpMod;
					this.player.jumping = true;
				}
			}

			// no motion on S/DOWN

			if (!(getConstant("D") in keysDown) && !(getConstant("RIGHT") in keysDown) && !(getConstant("A") in keysDown) && !(getConstant("LEFT") in keysDown)) {
				tempXVelocity = 0;
			}

			tempYVelocity += this.gravityVelocityMod;
			tempXVelocity *= deltaTime/5;

			if (tempYVelocity > this.terminalVelocity) {
				tempYVelocity = this.terminalVelocity;
			}

			this.player.yVelocity = tempYVelocity;
			var tempXPos = this.player.xPosition + tempXVelocity;
			var tempYPos = this.player.yPosition + tempYVelocity;

			// sketching out canvas movement...
			// currnetly this breaks collision detection if it happens while mapoffset adjusts
			// - maybe need to mix the new offset w/ old pos (xCur) to ensure proper calcs...
			// I believe it breaks due to early check against just pPos and not mOffset
			if (tempXPos > this.playerPosLimitRight) {
				this.tiledMapOffsets.oldOffsetX = this.tiledMapOffsets.curOffsetX;
//				this.oldOffsetX = this.mapOffsetX;
				this.tiledMapOffsets.curOffsetX -=  tempXPos-this.playerPosLimitRight;
//				this.mapOffsetX -= tempXPos-this.playerPosLimitRight;
				tempXPos = this.playerPosLimitRight;
			} else if (tempXPos < this.playerPosLimitLeft) {
				this.tiledMapOffsets.oldOffsetX = this.tiledMapOffsets.curOffsetX;
//				this.oldOffsetX = this.mapOffsetX;
				this.tiledMapOffsets.curOffsetX -= tempXPos-this.playerPosLimitLeft;
//				this.mapOffsetX -= tempXPos-this.playerPosLimitLeft;
				tempXPos = this.playerPosLimitLeft;
			}

			this.player.position.xNew = tempXPos;
			this.player.position.yNew = tempYPos;
			this.player.position.xCurrent = this.player.xPosition;
			this.player.position.yCurrent = this.player.yPosition;

			this.tiledMap.resolveCollision(this.player.position, this.tiledMapOffsets);

			if (this.player.position.yCurrent != tempYPos) {
				if (this.player.position.yCurrent < tempYPos) {
					this.player.jumping = false;
				}
				this.player.yVelocity = 0;
			}

			this.player.setPosition(this.player.position.xCurrent, this.player.position.yCurrent);

			if (this.initialized &&
				(this.player.position.xCurrent == (this.tiledMap.endX+this.tiledMapOffsets.curOffsetX)) &&
				(this.player.position.yCurrent == (this.tiledMap.endY+this.tiledMapOffsets.curOffsetY-32))) {
				this.completed = true;
			}
		} else { // level complete logic
			if (getConstant("SPACEBAR") in keysDown) {
				if (this.mapList.currentMapIndex == this.mapList.mapList.length) {
					this.newState = "menu";
					this.initialized = false;
					this.tiled = false;
					this.mapOffsetSet = false;
					this.completed = false;
					if (getConstant("SPACEBAR") in keysDown) {
						delete keysDown[getConstant("SPACEBAR")];
					}
					if (getConstant("ENTER") in keysDown) {
						delete keysDown[getConstant("ENTER")]
					}
				} else {
					this.initialized = false;
					this.tiled = false;
					this.mapOffsetSet = false;
					this.completed = false;
				}
			}
		}
	}

	this.prevTime = new Date().getTime();
};

PlaystateClass.prototype.render = function (canvasContext) {
	"use strict";
	var oldFont = canvasContext.font;

	canvasContext.fillStyle = "#5fa";
	canvasContext.fillRect(0,0,800,600);
	canvasContext.font = oldFont;

	if (this.initialized) {
		if (!this.completed) {
			this.tiledMap.renderMap(canvasContext, this.tiledMapOffsets.curOffsetX, this.tiledMapOffsets.curOffsetY);
			this.player.render(canvasContext);
		} else {
			// end level screen
			canvasContext.fillStyle = "#000";
			canvasContext.fillRect(0,0,800,600);
			canvasContext.fillStyle = "#fff";
			canvasContext.font = "27px Helvetica";
			canvasContext.fillText("Level complete!", 312, 250);
		}
	} else {
		canvasContext.font = "48px Helvetica";
		canvasContext.fillStyle = "#FFF";
		canvasContext.strokeStyle = "#000";
		canvasContext.fillText("Loading...",295,300);
		canvasContext.strokeText("Loading...",295,300);
		canvasContext.font = oldFont;
	}
};
<commit_msg>tiledMapOffsets only used if mapwidth > 800<commit_after>// PlaystateClass.js
function PlaystateClass() {
	"use strict";
	this.stateName = "play";

	this.listed = false;
	this.mapList = new LPMapList();

	this.tiled = false;
	this.tiledMap = new LPTiledMap();
	this.tiledMapOffsets = new LPTiledMapOffsets();
	this.tiledMapOffsets.curOffsetX = -1;
	this.tiledMapOffsets.curOffsetY = -1;
	this.tiledMapOffsets.oldOffsetX = -1;
	this.tiledMapOffsets.oldOffsetY = -1;

	this.mapOffsetSet = false;
	this.initialized = false;
	this.completed = false;

	this.userInput = "";

	this.player = new LPPlayerCharacter();
	this.playerVelocityXMod = 2;
	this.inertiaMod = 5;
	this.playerVelocityJumpMod = 4;
	this.gravityVelocityMod = 0.1;
	this.terminalVelocity = 20;

	this.playerPosLimitLeft = 192;
	this.playerPosLimitRight = 608;

	this.jumpSnd = new Audio("res/snd/jump_02.ogg");
	this.prevTime = new Date().getTime();
}
PlaystateClass.prototype = new GamestateClass();
PlaystateClass.prototype.constructor = PlaystateClass;

PlaystateClass.prototype.initialize = function () {
	"use strict";
	this.listed = false;
	this.mapList = new LPMapList();

	this.tiled = false;
	this.tiledMap = new LPTiledMap();

	this.tiledMapOffsets.curOffsetX = -1;
	this.tiledMapOffsets.curOffsetY = -1;
	this.tiledMapOffsets.oldOffsetX = -1;
	this.tiledMapOffsets.oldOffsetY = -1;

	this.mapOffsetSet = false;
	this.initialized = false;
	this.completed = false;

	this.userInput = "";

	this.newState = "";
}

PlaystateClass.prototype.update = function (keysDown) {
	"use strict";
	if (this.newState != "") {
		this.initialize();
	}

	var deltaTime = new Date().getTime() - this.prevTime;
	if (!this.initialized) {
		if (!this.listed) {
			this.mapList.loadMapList("maplist");
			this.listed = true;
		}

		if (this.listed && this.mapList.mapListLoaded && !this.tiled) {
			this.tiledMap.loadMap(this.mapList.mapList[this.mapList.currentMapIndex++]);
			this.tiled = true;
		}

		if (this.tiled && this.tiledMap.mapLoaded) {
			if (this.tiledMap.mapPXHeight < 600) {
				this.tiledMapOffsets.curOffsetY = (600 - this.tiledMap.mapPXHeight)/2;
//				this.mapOffsetY = (600 - this.tiledMap.mapPXHeight)/2;
			} else {
				this.tiledMapOffsets.curOffsetY = 0;
			}
			if (this.tiledMap.mapPXWidth < 800) {
				this.tiledMapOffsets.curOffsetX = (800 - this.tiledMap.mapPXWidth)/2;
//				this.mapOffsetX = (800 - this.tiledMap.mapPXWidth)/2;
			} else {
				this.tiledMapOffsets.curOffsetX = 0;
			}

			this.mapOffsetSet = true;
		}

		if (this.mapOffsetSet) {
			this.player.setPosition(this.tiledMap.startX + this.tiledMapOffsets.curOffsetX, this.tiledMap.startY + this.tiledMapOffsets.curOffsetY - 32);
			this.initialized = true;
		}
	} else {
		if (!this.completed) {
			var tempXVelocity = this.player.xVelocity;
			var tempYVelocity = this.player.yVelocity;
			if (getConstant("A") in keysDown || getConstant("LEFT") in keysDown) {
				tempXVelocity -= this.playerVelocityXMod;
				if (tempXVelocity < -this.player.xMaxVelocity) {
					tempXVelocity = -this.player.xMaxVelocity;
				}
			}
			if (getConstant("D") in keysDown || getConstant("RIGHT") in keysDown) {
				tempXVelocity += this.playerVelocityXMod;
				if (tempXVelocity > this.player.xMaxVelocity) {
					tempXVelocity = this.player.xMaxVelocity;
				}
			}

			if (getConstant("W") in keysDown || getConstant("UP") in keysDown) {
				if (!this.player.jumping) {
					this.jumpSnd.play();
					tempYVelocity = -this.playerVelocityJumpMod;
					this.player.jumping = true;
				}
			}

			// no motion on S/DOWN

			if (!(getConstant("D") in keysDown) && !(getConstant("RIGHT") in keysDown) && !(getConstant("A") in keysDown) && !(getConstant("LEFT") in keysDown)) {
				tempXVelocity = 0;
			}

			tempYVelocity += this.gravityVelocityMod;
			tempXVelocity *= deltaTime/5;

			if (tempYVelocity > this.terminalVelocity) {
				tempYVelocity = this.terminalVelocity;
			}

			this.player.yVelocity = tempYVelocity;
			var tempXPos = this.player.xPosition + tempXVelocity;
			var tempYPos = this.player.yPosition + tempYVelocity;

			if (this.tiledMap.mapPXWidth > 800) {
				if (tempXPos > this.playerPosLimitRight) {
					this.tiledMapOffsets.oldOffsetX = this.tiledMapOffsets.curOffsetX;
					this.tiledMapOffsets.curOffsetX -=  tempXPos-this.playerPosLimitRight;
					tempXPos = this.playerPosLimitRight;
				} else if (tempXPos < this.playerPosLimitLeft) {
					this.tiledMapOffsets.oldOffsetX = this.tiledMapOffsets.curOffsetX;
					this.tiledMapOffsets.curOffsetX -= tempXPos-this.playerPosLimitLeft;
					tempXPos = this.playerPosLimitLeft;
				}
			}

			this.player.position.xNew = tempXPos;
			this.player.position.yNew = tempYPos;
			this.player.position.xCurrent = this.player.xPosition;
			this.player.position.yCurrent = this.player.yPosition;

			this.tiledMap.resolveCollision(this.player.position, this.tiledMapOffsets);

			if (this.player.position.yCurrent != tempYPos) {
				if (this.player.position.yCurrent < tempYPos) {
					this.player.jumping = false;
				}
				this.player.yVelocity = 0;
			}

			this.player.setPosition(this.player.position.xCurrent, this.player.position.yCurrent);

			if (this.initialized &&
				(this.player.position.xCurrent == (this.tiledMap.endX+this.tiledMapOffsets.curOffsetX)) &&
				(this.player.position.yCurrent == (this.tiledMap.endY+this.tiledMapOffsets.curOffsetY-32))) {
				this.completed = true;
			}
		} else { // level complete logic
			if (getConstant("SPACEBAR") in keysDown) {
				if (this.mapList.currentMapIndex == this.mapList.mapList.length) {
					this.newState = "menu";
					this.initialized = false;
					this.tiled = false;
					this.mapOffsetSet = false;
					this.completed = false;
					if (getConstant("SPACEBAR") in keysDown) {
						delete keysDown[getConstant("SPACEBAR")];
					}
					if (getConstant("ENTER") in keysDown) {
						delete keysDown[getConstant("ENTER")]
					}
				} else {
					this.initialized = false;
					this.tiled = false;
					this.mapOffsetSet = false;
					this.completed = false;
				}
			}
		}
	}

	this.prevTime = new Date().getTime();
};

PlaystateClass.prototype.render = function (canvasContext) {
	"use strict";
	var oldFont = canvasContext.font;

	canvasContext.fillStyle = "#5fa";
	canvasContext.fillRect(0,0,800,600);
	canvasContext.font = oldFont;

	if (this.initialized) {
		if (!this.completed) {
			this.tiledMap.renderMap(canvasContext, this.tiledMapOffsets.curOffsetX, this.tiledMapOffsets.curOffsetY);
			this.player.render(canvasContext);
		} else {
			// end level screen
			canvasContext.fillStyle = "#000";
			canvasContext.fillRect(0,0,800,600);
			canvasContext.fillStyle = "#fff";
			canvasContext.font = "27px Helvetica";
			canvasContext.fillText("Level complete!", 312, 250);
		}
	} else {
		canvasContext.font = "48px Helvetica";
		canvasContext.fillStyle = "#FFF";
		canvasContext.strokeStyle = "#000";
		canvasContext.fillText("Loading...",295,300);
		canvasContext.strokeText("Loading...",295,300);
		canvasContext.font = oldFont;
	}
};
<|endoftext|>


<commit_before>var cloak = require('cloak');
var _ = require('underscore');

cloak.configure({
  port: 8090,
  roomLife: 1000*60*60*3,

  autoJoinLobby: false,
  minRoomMembers: 1,
  pruneEmptyRooms: 1000,
  reconnectWait: null,

  messages: {
    registerUsername: function(arg, user) {
      var users = cloak.getUsers();
      var username = arg.username;
      var usernames = _.pluck(users, 'name');
      var success = false;
      if (_.indexOf(usernames, username) === -1) {
        console.log('creating ', username, ' ', arg.userid)
        success = true;
        user.name = username;
        user.id = arg.userid;
        user.message('registerUsernameResponse', [success, username]);
      }else{
        console.log('resuming ', username, ' ', arg.userid)
        //resume so add back to the lobby
        _.each(users, function(usr) {
          if(usr.id == arg.userid){
            usr.delete();
            user.name = username;
            user.id = arg.userid;
          }
        })
        var successadd = cloak.getLobby().addMember(user);
        user.message('joinLobbyResponse', successadd);
        if(successadd)
          cloak.messageAll('refreshAll');
      }
    },

    joinLobby: function(arg, user) {
      var users = cloak.getUsers();
      var usernames = _.pluck(users, 'name');
      if(_.indexOf(usernames, user.name) !== -1)
      {
        var success = cloak.getLobby().addMember(user);
        user.message('joinLobbyResponse', success);
        if(success)
          cloak.messageAll('refreshAll');
      }
    },

    listUsers: function(arg, user){
      if(user.room.getMembers){
      	user.message('refreshLobby', {
          users: user.room.getMembers(true),
          inLobby: user.room.isLobby,
          roomCount: user.room.getMembers().length,
          roomSize: user.room.size
        });
      }
    },

    listRooms: function(arg, user){
      user.message('refreshRooms', cloak.getRooms(true));
    },

    createRoom: function(arg, user) {
      var room = cloak.createRoom(arg.name, 2);
      var success = room.addMember(user);
      user.message('roomCreated', {
        success: success,
        roomId: room.id
      });
      cloak.messageAll('refreshAll');
    },

    joinRoom: function(id, user) {
      var room = cloak.getRoom(id)
      var memCount = room.getMembers().length;
      if (memCount < 2)
      {
        room.addMember(user);
        user.message('joinRoomResponse', {
          id: id,
          success: true
        });
      } else {
        user.message('joinRoomResponse', {
          id: id,
          success: false
        });
      }
    },

    leaveRoom: function(arg, user){
      user.leaveRoom();
      cloak.messageAll('refreshAll');
    },

    disconnectUser: function(arg,user){
      console.log('deleting ', user);
      user.delete();
    },

    chat: function(msg, user) {
      user.getRoom().messageMembers('chat', [msg, user.name]);
    },

    refreshRoom: function(arg, user) {
      user.message('refreshRoomResponse', user.room.getMembers(true));
    },

    turnDone: function(move, user){
      //move[0] is from and move[1] is to
      // If it's currently the turn of the user and they say they're done, advance the turn
      if (user.team === user.room.turn) {
        user.room.turn = (user.room.turn === 'muon') ? 'antimuon' : 'muon';
      }
      // let the other player know
      var otherPlayer = _.reject(user.room.members, function(member) {
        return member.id === user.id;
      });
      console.log("sending move to player:", otherPlayer[0]);
      user.room.moveHist.push({from: move[0], to: move[1]});
      otherPlayer[0].message('performOpponentMove', [move[0],move[1]]);

      user.room.messageMembers('turn', user.room.turn);

    },
    proposeDraw: function(arg, user){
      user.room.messageMembers('proposeDrawResponse', [user.name]);
    },
    respondToDraw: function(accept, user){
      user.room.messageMembers('respondToDrawResponse', [accept]);
    },
    proposeRematch: function(arg, user){
      user.room.messageMembers('proposeRematchResponse', [user.name]);
    },
    respondToRematch: function(accept, user){
      user.room.messageMembers('respondToRematchResponse', [accept]);
    }
  },

  room: {
    init: function() {
      this.turn = 'muon';
      this.moveHist = [];
      this.teams = {
        muon: '',
        antimuon: ''
      };

      console.log('created room ' + this.id);
      this.data.lastReportedAge = 0;
    },

    newMember: function(user) {
      if (this.teams.muon === '') { //host
        this.teams.muon = user.id;
        user.team = 'muon';
        user.message('userMessage', 'your are muons and your id is ' + user.id);
      }
      else if (this.teams.antimuon === '') { //client
        this.teams.antimuon = user.id;
        user.team = 'antimuon';
        user.message('userMessage', 'your team is antimuons and your id is ' + user.id);
      }
      else {
        var msg = 'Um, we tried to assign a team member but all teams were taken for this room.';
        console.log(msg);
        user.team = 'none';
        user.message('userMessage', msg);
      }
      user.message('assignTeam', {
        team: user.team,
        turn: this.turn,
        hist: this.moveHist
      });
    },

    memberLeaves: function(user) {
      if(user.team == 'muon')
        this.teams.muon = '';
      else if(user.team == 'antimuon')
        this.teams.antimuon = '';

      // if we have 0 people in the room, close the room
      if (this.getMembers().length <= 0) {
        this.delete();
        //refresh the lobby
      }
      cloak.messageAll('refreshAll');
    },

    pulse: function() {
      // add timed turn stuff here
    },

    close: function() {
      this.messageMembers('you have left ' + this.name);
    }

  },
  lobby: {
    pulse: function() {
      // add timed turn stuff here

    }
  }

});

cloak.run();
<commit_msg>added more logging<commit_after>var cloak = require('cloak');
var _ = require('underscore');

cloak.configure({
  port: 8090,
  roomLife: 1000*60*60*3,

  autoJoinLobby: false,
  minRoomMembers: 1,
  pruneEmptyRooms: 1000,
  reconnectWait: null,

  messages: {
    registerUsername: function(arg, user) {
      var users = cloak.getUsers();
      var username = arg.username;
      var usernames = _.pluck(users, 'name');
      var success = false;
      if (_.indexOf(usernames, username) === -1) {
        console.log('creating ', username, ' ', arg.userid)
        success = true;
        user.name = username;
        user.id = arg.userid;
        user.message('registerUsernameResponse', [success, username]);
      }else{
        console.log('resuming ', username, ' ', arg.userid)
        //resume so add back to the lobby
        _.each(users, function(usr) {
          if(usr.id == arg.userid){
            usr.delete();
            user.name = username;
            user.id = arg.userid;
          }
        })
        var successadd = cloak.getLobby().addMember(user);
        user.message('joinLobbyResponse', successadd);
        if(successadd)
          cloak.messageAll('refreshAll');
      }
    },

    joinLobby: function(arg, user) {
      var users = cloak.getUsers();
      var usernames = _.pluck(users, 'name');
      if(_.indexOf(usernames, user.name) !== -1)
      {
        var success = cloak.getLobby().addMember(user);
        user.message('joinLobbyResponse', success);
        if(success)
          cloak.messageAll('refreshAll');
      }
    },

    listUsers: function(arg, user){
      if(user.room.getMembers){
      	user.message('refreshLobby', {
          users: user.room.getMembers(true),
          inLobby: user.room.isLobby,
          roomCount: user.room.getMembers().length,
          roomSize: user.room.size
        });
      }
    },

    listRooms: function(arg, user){
      user.message('refreshRooms', cloak.getRooms(true));
    },

    createRoom: function(arg, user) {
      var room = cloak.createRoom(arg.name, 2);
      var success = room.addMember(user);
      user.message('roomCreated', {
        success: success,
        roomId: room.id
      });
      cloak.messageAll('refreshAll');
    },

    joinRoom: function(id, user) {
      var room = cloak.getRoom(id)
      var memCount = room.getMembers().length;
      if (memCount < 2)
      {
        room.addMember(user);
        user.message('joinRoomResponse', {
          id: id,
          success: true
        });
      } else {
        user.message('joinRoomResponse', {
          id: id,
          success: false
        });
      }
    },

    leaveRoom: function(arg, user){
      user.leaveRoom();
      cloak.messageAll('refreshAll');
    },

    disconnectUser: function(arg,user){
      console.log('deleting ', user);
      user.delete();
    },

    chat: function(msg, user) {
      user.getRoom().messageMembers('chat', [msg, user.name]);
    },

    refreshRoom: function(arg, user) {
      user.message('refreshRoomResponse', user.room.getMembers(true));
    },

    turnDone: function(move, user){
      //move[0] is from and move[1] is to
      // If it's currently the turn of the user and they say they're done, advance the turn
      if (user.team === user.room.turn) {
        user.room.turn = (user.room.turn === 'muon') ? 'antimuon' : 'muon';
      }
      // let the other player know
      var otherPlayer = _.reject(user.room.members, function(member) {
        return member.id === user.id;
      });
      console.log("sending move to player:", otherPlayer[0]);
      user.room.moveHist.push({from: move[0], to: move[1]});
      otherPlayer[0].message('performOpponentMove', [move[0],move[1]]);

      user.room.messageMembers('turn', user.room.turn);

    },
    proposeDraw: function(arg, user){
      user.room.messageMembers('proposeDrawResponse', [user.name]);
    },
    respondToDraw: function(accept, user){
      user.room.messageMembers('respondToDrawResponse', [accept]);
    },
    proposeRematch: function(arg, user){
      user.room.messageMembers('proposeRematchResponse', [user.name]);
    },
    respondToRematch: function(accept, user){
      user.room.messageMembers('respondToRematchResponse', [accept]);
    }
  },

  room: {
    init: function() {
      this.turn = 'muon';
      this.moveHist = [];
      this.teams = {
        muon: '',
        antimuon: ''
      };

      console.log('created room ' + this.id);
      this.data.lastReportedAge = 0;
    },

    newMember: function(user) {
      if (this.teams.muon === '') { //host
        this.teams.muon = user.id;
        user.team = 'muon';
        user.message('userMessage', 'your are muons and your id is ' + user.id);
      }
      else if (this.teams.antimuon === '') { //client
        this.teams.antimuon = user.id;
        user.team = 'antimuon';
        user.message('userMessage', 'your team is antimuons and your id is ' + user.id);
      }
      else {
        var msg = 'Um, we tried to assign a team member but all teams were taken for this room.';
        console.log(msg);
        user.team = 'none';
        user.message('userMessage', msg);
      }
      user.message('assignTeam', {
        team: user.team,
        turn: this.turn,
        hist: this.moveHist
      });
    },

    memberLeaves: function(user) {
      if(user.team == 'muon')
        this.teams.muon = '';
      else if(user.team == 'antimuon')
        this.teams.antimuon = '';

      // if we have 0 people in the room, close the room
      if (this.getMembers().length <= 0) {
        console.log('deleting room ', this.id)
        this.delete();
      }
      cloak.messageAll('refreshAll');
    },

    pulse: function() {
      // add timed turn stuff here
    },

    close: function() {
      this.messageMembers('you have left ' + this.name);
    }

  },
  lobby: {
    pulse: function() {
      // add timed turn stuff here

    }
  }

});

cloak.run();
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013 Clemens Buchacher
 *
 * Much inspired by Alexey Lebedev's answer in
 * http://stackoverflow.com/questions/12445579/javascript-window-find-does-not-select-search-term.
 */

function visibleTextNodeWalker(topNode) {
	function acceptNode(node) {
		if (node.nodeType == Node.TEXT_NODE)
			return NodeFilter.FILTER_ACCEPT;
		else if (node.offsetWidth && node.offsetHeight && node.style.visibility != 'hidden')
			return NodeFilter.FILTER_SKIP;
		else
			return NodeFilter.FILTER_REJECT;
	}
	return document.createTreeWalker(topNode, NodeFilter.SHOW_ALL, acceptNode, false);
}

function makeNodeArray(walker) {
	var nodeArray = [];
	while (walker.nextNode()) {
		nodeArray.push(walker.currentNode);
	}

	return nodeArray;
}

function visibleTextNodes(topNode) {
	return makeNodeArray(visibleTextNodeWalker(topNode));
}

function nodeValues(nodes) {
	var values = [];
	for (var i = 0; i < nodes.length; i++) {
		values.push(nodes[i].nodeValue);
	}
	return values;
}

function visibleText(nodes) {
	return nodeValues(nodes).join("");
}

function nodeAt(nodeArray, index) {
	var pos = 0;
	for (var i = 0; i < nodeArray.length; i++) {
		var node = nodeArray[i];
		var next_pos = pos + node.nodeValue.length;
		if (index < next_pos) {
			return [node, index - pos];
		}

		pos = next_pos;
	}

	return null;
}

function indexAt(textNodes, range, forward) {
	var node, offset;

	if (forward) {
		node = range.endContainer;
		offset = range.endOffset;
	} else {
		node = range.startContainer;
		offset = range.startOffset;
	}

	var index = textNodes.indexOf(node);
	if (index < 0) {
		return 0;
	}

	return visibleText(textNodes.slice(0, index)).length + offset;
}

function find_substring_1(text, pattern, forward, startOffset) {
	if (forward) {
		return text.indexOf(pattern, startOffset);
	} else {
		var offset = startOffset - pattern.length;
		if (offset < 0) {
			return -1;
		}
		return text.lastIndexOf(pattern, offset);
	}
}

function find_substring(text, pattern, forward, startOffset) {
	var wraparound = false;
	var index = find_substring_1(text, pattern, forward, startOffset);
	if (index < 0) {
		wraparound = true;
		startOffset = 0;
		if (!forward) {
			startOffset = text.length+1;
		}
		index = find_substring_1(text, pattern, forward, startOffset);
	}

	if (index < 0)
		return null;

	return {
		"startOffset": index,
		"endOffset": index + pattern.length,
		"wraparound": wraparound
	};
}

function find_regex(text, pattern, forward, startOffset) {
	var index = -1;
	var endOffset = -1;
	var wraparound = false;

	try {
		var re = new RegExp(pattern, "g");
	} catch (error) {
		return null;
	}
	re.lastIndex = startOffset;
	while (true) {
		/* If an empty string matches, lastIndex is not incremented. */
		var prevLastIndex;
		if (prevLastIndex !== null && re.lastIndex == prevLastIndex) {
			re.lastIndex += 1;
		}
		prevLastIndex = re.lastIndex;

		var match = re.exec(text);

		if (match == null && startOffset > 0 && !wraparound) {
			wraparound = true;
			match = re.exec(text);
		}
		if (match == null) {
			break;
		}

		if (forward) {
			/* Done with the first match. */
			index = match.index;
			endOffset = re.lastIndex;
			break;
		} else if (wraparound && match.index >= startOffset) {
			/*
			 * We wrapped around and passed startOffset. We
			 * already found the first backward match in the
			 * previous iteration.
			 */
			break;
		} else {
			/*
			 * Keep track of the last match until we pass
			 * startOffset.
			 */
			index = match.index;
			endOffset = re.lastIndex;
		}
	}

	return {
		"startOffset": index,
		"endOffset": endOffset,
		"wraparound": wraparound
	};
}

function find_match(text, regex, pattern, forward, startOffset) {
	if (regex) {
		return find_regex(text, pattern, forward, startOffset);
	} else {
		return find_substring(text, pattern, forward, startOffset);
	}
}

function search(regex, pattern, forward, startAt) {
	if (forward == null) {
		forward = true;
	}
	var textNodes = visibleTextNodes(document.body);
	var startOffset = 0;
	if (startAt !== null) {
		startOffset = indexAt(textNodes, startAt, forward);
	}

	var text = visibleText(textNodes);
	var match = find_match(text, regex, pattern, forward, startOffset);
	if (match == null) {
		return null;
	}

	var range = document.createRange();
	range.setStart.apply(range, nodeAt(textNodes, match.startOffset));
	range.setEnd.apply(range, nodeAt(textNodes, match.endOffset));

	return { "range": range, "wraparound": match.wraparound };
}

function select(range) {
	window.getSelection().removeAllRanges();
	window.getSelection().addRange(range);
}

function select_match(regex, pattern, forward) {
	var selection = window.getSelection();
	var startAt = null;
	if (selection.type != "None") {
		startAt = selection.getRangeAt(0);
	}

	var match = search(regex, pattern, forward, startAt);
	if (match !== null) {
		select(match.range)
	}

	return match;
}
<commit_msg>workaround in case we get stuck with an invalid selection<commit_after>/*
 * Copyright (C) 2013 Clemens Buchacher
 *
 * Much inspired by Alexey Lebedev's answer in
 * http://stackoverflow.com/questions/12445579/javascript-window-find-does-not-select-search-term.
 */

function visibleTextNodeWalker(topNode) {
	function acceptNode(node) {
		if (node.nodeType == Node.TEXT_NODE)
			return NodeFilter.FILTER_ACCEPT;
		else if (node.offsetWidth && node.offsetHeight && node.style.visibility != 'hidden')
			return NodeFilter.FILTER_SKIP;
		else
			return NodeFilter.FILTER_REJECT;
	}
	return document.createTreeWalker(topNode, NodeFilter.SHOW_ALL, acceptNode, false);
}

function makeNodeArray(walker) {
	var nodeArray = [];
	while (walker.nextNode()) {
		nodeArray.push(walker.currentNode);
	}

	return nodeArray;
}

function visibleTextNodes(topNode) {
	return makeNodeArray(visibleTextNodeWalker(topNode));
}

function nodeValues(nodes) {
	var values = [];
	for (var i = 0; i < nodes.length; i++) {
		values.push(nodes[i].nodeValue);
	}
	return values;
}

function visibleText(nodes) {
	return nodeValues(nodes).join("");
}

function nodeAt(nodeArray, index) {
	var pos = 0;
	for (var i = 0; i < nodeArray.length; i++) {
		var node = nodeArray[i];
		var next_pos = pos + node.nodeValue.length;
		if (index < next_pos) {
			return [node, index - pos];
		}

		pos = next_pos;
	}

	return null;
}

function indexAt(textNodes, range, forward) {
	var node, offset;

	if (forward) {
		node = range.endContainer;
		offset = range.endOffset;
	} else {
		node = range.startContainer;
		offset = range.startOffset;
	}

	var index = textNodes.indexOf(node);
	if (index < 0) {
		return 0;
	}

	return visibleText(textNodes.slice(0, index)).length + offset;
}

function find_substring_1(text, pattern, forward, startOffset) {
	if (forward) {
		return text.indexOf(pattern, startOffset);
	} else {
		var offset = startOffset - pattern.length;
		if (offset < 0) {
			return -1;
		}
		return text.lastIndexOf(pattern, offset);
	}
}

function find_substring(text, pattern, forward, startOffset) {
	var wraparound = false;
	var index = find_substring_1(text, pattern, forward, startOffset);
	if (index < 0) {
		wraparound = true;
		startOffset = 0;
		if (!forward) {
			startOffset = text.length+1;
		}
		index = find_substring_1(text, pattern, forward, startOffset);
	}

	if (index < 0)
		return null;

	return {
		"startOffset": index,
		"endOffset": index + pattern.length,
		"wraparound": wraparound
	};
}

function find_regex(text, pattern, forward, startOffset) {
	var index = -1;
	var endOffset = -1;
	var wraparound = false;

	try {
		var re = new RegExp(pattern, "g");
	} catch (error) {
		return null;
	}
	re.lastIndex = startOffset;
	while (true) {
		/* If an empty string matches, lastIndex is not incremented. */
		var prevLastIndex;
		if (prevLastIndex !== null && re.lastIndex == prevLastIndex) {
			re.lastIndex += 1;
		}
		prevLastIndex = re.lastIndex;

		var match = re.exec(text);

		if (match == null && startOffset > 0 && !wraparound) {
			wraparound = true;
			match = re.exec(text);
		}
		if (match == null) {
			break;
		}

		if (forward) {
			/* Done with the first match. */
			index = match.index;
			endOffset = re.lastIndex;
			break;
		} else if (wraparound && match.index >= startOffset) {
			/*
			 * We wrapped around and passed startOffset. We
			 * already found the first backward match in the
			 * previous iteration.
			 */
			break;
		} else {
			/*
			 * Keep track of the last match until we pass
			 * startOffset.
			 */
			index = match.index;
			endOffset = re.lastIndex;
		}
	}

	return {
		"startOffset": index,
		"endOffset": endOffset,
		"wraparound": wraparound
	};
}

function find_match(text, regex, pattern, forward, startOffset) {
	if (regex) {
		return find_regex(text, pattern, forward, startOffset);
	} else {
		return find_substring(text, pattern, forward, startOffset);
	}
}

function search(regex, pattern, forward, startAt) {
	if (forward == null) {
		forward = true;
	}
	var textNodes = visibleTextNodes(document.body);
	var startOffset = 0;
	if (startAt !== null) {
		startOffset = indexAt(textNodes, startAt, forward);
	}

	var text = visibleText(textNodes);
	var match = find_match(text, regex, pattern, forward, startOffset);
	if (match == null) {
		return null;
	}

	var range = document.createRange();
	range.setStart.apply(range, nodeAt(textNodes, match.startOffset));
	range.setEnd.apply(range, nodeAt(textNodes, match.endOffset));

	return { "range": range, "wraparound": match.wraparound };
}

function select(range) {
	window.getSelection().removeAllRanges();
	window.getSelection().addRange(range);
}

function selectedRange() {
	var selection = window.getSelection();
	if (selection.type == "None") {
		return null;
	}
	return selection.getRangeAt(0);
}

function compareRange(a, b) {
	var startsame = a.startContainer == b.startContainer && a.startOffset == b.startOffset;
	var endsame = a.endContainer == b.endContainer && a.endOffset == b.endOffset;
	return startsame || endsame;
}

function select_match(regex, pattern, forward) {
	var startAt = selectedRange();

	var match = search(regex, pattern, forward, startAt);
	if (match == null) {
		return null;
	}

	select(match.range);

	/*
	 * In case we find a range which cannot be selected properly, restart
	 * from the beginning.
	 */
	if (!compareRange(selectedRange(), match.range)) {
		window.getSelection().removeAllRanges();
		match = search(regex, pattern, forward, startAt);
	}

	return match;
}
<|endoftext|>


<commit_before>module.exports = function (graph, settings) {
  var merge = require('ngraph.merge');
  var vivasvg = require('vivasvg');

  settings = merge(settings, {
    physics: {
      springLength: 30,
      springCoeff: 0.0008,
      dragCoeff: 0.01,
      gravity: -1.2,
      theta: 1
    }
  });

  // todo: abstract this as scene
  var svgDoc = new vivasvg.Document(settings.container || document.body);
  var nodes = new vivasvg.Collection();
  var edges = new vivasvg.Collection();

  var layout = getDefaultLayout();
  var disposed = false;
  var sceneInitialized = false;
  var _nodeTemplate, _linkTemplate;

  return {
    run: animationLoop,

    renderOneFrame: renderOneFrame,

    dispose: function () {
      layout.dispose();
      disposed = true;

      listenToGraphEvents(false);
    },

    nodeTemplate: function (template) {
      _nodeTemplate = template;
    },

    linkTemplate: function (template) {
      _linkTemplate = template;
    }
  };

  function animationLoop() {
    requestAnimationFrame(animationLoop);
    layout.step(); // TODO: this should stop, when layout is stable
    renderOneFrame();
  }

  function renderOneFrame() {
    if (disposed) return;
    if (!sceneInitialized) initializeScene();

    svgDoc.render();
  }

  function getDefaultLayout() {
    if (settings.layout) return settings.layout;
    var createLayout = require('ngraph.forcelayout');
    var physics = require('ngraph.physics.simulator');
    return createLayout(graph, physics(settings.physics));
  }

  function initializeScene() {
    sceneInitialized = true;

    graph.forEachNode(addNode);
    graph.forEachLink(addLink);

    var edgesUI = new vivasvg.ItemsControl();
    edgesUI.setItemTemplate(_linkTemplate);
    edgesUI.setItemSource(edges);
    svgDoc.appendChild(edgesUI);

    var nodesUI = new vivasvg.ItemsControl();
    nodesUI.setItemTemplate(_nodeTemplate);
    nodesUI.setItemSource(nodes);
    svgDoc.appendChild(nodesUI);

    listenToGraphEvents(true);
  }

  function addNode(node) {
    nodes.push(node);
  }

  function removeNode(node) {
  }

  function addLink(link) {
    edges.push(link);
  }

  function removeLink(node) {

  }

  function listenToGraphEvents(isOn) {
    graph[isOn ? 'on': 'off']('changed', onGraphChanged);
  }

  function onGraphChanged(changes) {
    for (var i = 0; i < changes.length; ++i) {
      var change = changes[i];
      if (change.changeType === 'add') {
        if (change.node) {
          addNode(change.node);
        }
        if (change.link) {
          addLink(change.link);
        }
      } else if (change.changeType === 'remove') {
        if (change.node) {
          removeNode(change.node);
        }
        if (change.link) {
          removeLink(change.link);
        }
      }
    }
  }
};
<commit_msg>Notifying about node position update<commit_after>module.exports = function (graph, settings) {
  var merge = require('ngraph.merge');
  var vivasvg = require('vivasvg');

  settings = merge(settings, {
    physics: {
      springLength: 30,
      springCoeff: 0.0008,
      dragCoeff: 0.01,
      gravity: -1.2,
      theta: 1
    }
  });

  // todo: abstract this as scene
  var svgDoc = new vivasvg.Document(settings.container || document.body);
  var nodes = new vivasvg.Collection();
  var edges = new vivasvg.Collection();

  var layout = getDefaultLayout();
  var disposed = false;
  var sceneInitialized = false;
  var _nodeTemplate, _linkTemplate;

  return {
    run: animationLoop,

    renderOneFrame: renderOneFrame,

    dispose: function () {
      layout.dispose();
      disposed = true;

      listenToGraphEvents(false);
    },

    nodeTemplate: function (template) {
      _nodeTemplate = template;
    },

    linkTemplate: function (template) {
      _linkTemplate = template;
    }
  };

  function animationLoop() {
    requestAnimationFrame(animationLoop);
    layout.step(); // TODO: this should stop, when layout is stable
    renderOneFrame();
  }

  function renderOneFrame() {
    if (disposed) return;
    if (!sceneInitialized) initializeScene();
    nodes.forEach(notifyNodePositionChange);
  }

  function notifyNodePositionChange(node) {
    node.fire('pos');
  }

  function getDefaultLayout() {
    if (settings.layout) return settings.layout;
    var createLayout = require('ngraph.forcelayout');
    var physics = require('ngraph.physics.simulator');
    return createLayout(graph, physics(settings.physics));
  }

  function initializeScene() {
    sceneInitialized = true;

    graph.forEachNode(addNode);
    graph.forEachLink(addLink);

    var edgesUI = new vivasvg.ItemsControl();
    edgesUI.setItemTemplate(_linkTemplate);
    edgesUI.setItemSource(edges);
    svgDoc.appendChild(edgesUI);

    var nodesUI = new vivasvg.ItemsControl();
    nodesUI.setItemTemplate('<g transform="translate({{pos.x}}, {{pos.y}})">' + _nodeTemplate + '</g>');
    nodesUI.setItemSource(nodes);
    svgDoc.appendChild(nodesUI);

    listenToGraphEvents(true);
  }

  function addNode(node) {
    nodes.push(vivasvg.model({
      pos: layout.getNodePosition(node.id),
      id: node.id,
      node: node
    }));
  }

  function removeNode(node) {
  }

  function addLink(link) {
    edges.push(link);
  }

  function removeLink(node) {

  }

  function listenToGraphEvents(isOn) {
    graph[isOn ? 'on': 'off']('changed', onGraphChanged);
  }

  function onGraphChanged(changes) {
    for (var i = 0; i < changes.length; ++i) {
      var change = changes[i];
      if (change.changeType === 'add') {
        if (change.node) {
          addNode(change.node);
        }
        if (change.link) {
          addLink(change.link);
        }
      } else if (change.changeType === 'remove') {
        if (change.node) {
          removeNode(change.node);
        }
        if (change.link) {
          removeLink(change.link);
        }
      }
    }
  }
};
<|endoftext|>


<commit_before>tm.main(function() {
    var app = tm.hybrid.Application("#canvas2d", "#canvas3d");
    app.resize(640, 960).fitWindow().run();

    app.replaceScene(tm.game.LoadingScene({
        width: 640, height: 960,
        assets: {
            // 拡張子による判別がきかないため、typeパラメータをつける
            kirara: {
                type: "three",
                url: "./assets/kirara.json",
            },

            hiyoko: "./assets/hiyoco_nomal_full.png",
        },
        nextScene: KiraraOnStage,
    }));
});

tm.define("KiraraOnStage", {
    superClass: "tm.hybrid.Scene", // tm.app.Sceneの3D向け拡張
    init: function() {
        this.superInit();

        // カメラ調整
        this.camera.setPosition(0, 20, 50);
        this.camera.lookAt(new THREE.Vector3(0, 10, 0));

        // ライトを動かす
        this.directionalLight
            .on("enterframe", function(e) {
                var f = e.app.frame;
                this.x = Math.cos(f * 0.1) * 10;
                this.z = Math.sin(f * 0.1) * 10;
            });

        // メッシュを表示する
        var kirara = tm.hybrid.Mesh("kirara") // Spriteっぽく使える
            .addChildTo(this)
            .on("enterframe", function() {
                if (this.rolling) this.rotationY += 10; // Y軸回転
            });
        kirara.rolling = false;

        // tweenerも使える
        kirara.tweener.clear()
            .to({ scaleY: 0.9 }, 300, "easeInBack")
            .to({ scaleY: 1.0 }, 300, "easeOutBack")
            .setLoop(true);

        // 2Dスプライトとの併用も可能
        var hiyoko = tm.display.Sprite("hiyoko", 32, 32)
            .setScale(4)
            .setFrameIndex(0)
            .addChildTo(this)
            .on("enterframe", function() {
                this.x += this.vx * 10;
                this.y += this.vy * 10;
                if (this.x < 0 || 640 < this.x) this.vx *= -1;
                if (this.y < 0 || 960 < this.y) this.vy *= -1;

                this.frameIndex = (this.frameIndex + 1) % 4;
                this.rotation += 2;
            });
        hiyoko.vx = 1;
        hiyoko.vy = 1;

        tm.ui.FlatButton({ text: "かいてん" })
            .setPosition(320, 100)
            .addChildTo(this)
            .on("push", function() {
                kirara.rolling = !kirara.rolling;
                this.label.text = kirara.rolling ? "とまる" : "かいてん";
            });
    }
});
<commit_msg>correct test program<commit_after>tm.main(function() {
    var app = tm.hybrid.Application("#canvas2d", "#canvas3d");
    app.resize(640, 960).fitWindow().run();

    app.replaceScene(tm.game.LoadingScene({
        width: 640, height: 960,
        assets: {
            // 拡張子による判別がきかないため、typeパラメータをつける
            kirara: {
                type: "three",
                url: "assets/kirara.json",
            },

            hiyoko: "assets/hiyoco_nomal_full.png",

            gradriel: "assets/gradriel_pose.mqo",
        },
        nextScene: KiraraOnStage,
    }));
});

tm.define("KiraraOnStage", {
    superClass: "tm.hybrid.Scene", // tm.app.Sceneの3D向け拡張
    init: function() {
        this.superInit();

        // カメラ調整
        this.camera.setPosition(0, 20, 50);
        this.camera.lookAt(new THREE.Vector3(0, 10, 0));

        // ライトを動かす
        this.directionalLight
            .on("enterframe", function(e) {
                var f = e.app.frame;
                this.x = Math.cos(f * 0.1) * 10;
                this.z = Math.sin(f * 0.1) * 10;
            });

        // メッシュを表示する
        var kirara = tm.hybrid.Mesh("kirara") // Spriteっぽく使える
            .addChildTo(this)
            .on("enterframe", function() {
                if (this.rolling) this.rotationY += 10; // Y軸回転
            });
        kirara.rolling = false;

        // tweenerも使える
        kirara.tweener.clear()
            .to({ scaleY: 0.9 }, 300, "easeInBack")
            .to({ scaleY: 1.0 }, 300, "easeOutBack")
            .setLoop(true);

        // 2Dスプライトとの併用も可能
        var hiyoko = tm.display.Sprite("hiyoko", 32, 32)
            .setScale(4)
            .setFrameIndex(0)
            .addChildTo(this)
            .on("enterframe", function() {
                this.x += this.vx * 10;
                this.y += this.vy * 10;
                if (this.x < 0 || 640 < this.x) this.vx *= -1;
                if (this.y < 0 || 960 < this.y) this.vy *= -1;

                this.frameIndex = (this.frameIndex + 1) % 4;
                this.rotation += 2;
            });
        hiyoko.vx = 1;
        hiyoko.vy = 1;

        tm.ui.FlatButton({ text: "かいてん" })
            .setPosition(320, 100)
            .addChildTo(this)
            .on("push", function() {
                kirara.rolling = !kirara.rolling;
                this.label.text = kirara.rolling ? "とまる" : "かいてん";
            });
    }
});
<|endoftext|>