<commit_before>import React from 'react';
import FluxComponent from 'flummox/component';

import AudioPlayback from './components/AudioPlayback';
import AudioPicker from './components/AudioPicker';
import SaveLoadForm from './components/SaveLoadForm';
import Chart from './components/Chart';

const resolutions = [24, 12, 8, 6, 4, 3];

class Editor extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      offset: 0,
      scrollResolutionIdx: 0,
      beatSpacing: 80
    };
  }

  getOffset() {
    if (this.props.inPlayback) {
      return this.props.playbackOffset;
    } else {
      return this.state.offset;
    }
  }

  getScrollResolution() {
    return resolutions[this.state.scrollResolutionIdx];
  }

  getNumMeasures() {
    const len = this.props.flux.getStore('audio').getLength();

    if (!len) {
      // No audio == no measures
      return null;
    }

    return this.props.flux.getStore('editor').getNumMeasures(len);
  }

  handleToggleNote(column) {
    this.props.flux.getActions('editor').toggleNote(this.state.offset, column);
  }

  handleUpdateScrollResolution(increase) {
    const inc = increase ? 1 : -1;

    const nextResIdx = this.state.scrollResolutionIdx + inc;

    if (nextResIdx < 0 || nextResIdx >= resolutions.length) {
      return;
    }

    const nextRes = resolutions[nextResIdx];

    // Snap to nearest note in resolution if resolution changes
    // i.e. if you're on an 8th and drop 8ths to 4ths, snap to previous 4th
    //      if you're on an 8th and drop 16ths to 8ths, stay on the same

    let nextOffset = this.state.offset - (this.state.offset % nextRes);

    this.setState({
      scrollResolutionIdx: nextResIdx,
      offset: nextOffset
    });
  }

  handleKeyPress(e) {
    e.preventDefault();
    const PG_UP = 33;
    const PG_DOWN = 34;
    const UP_ARROW = 38;
    const DOWN_ARROW = 40;

    const LEFT_ARROW = 37;
    const RIGHT_ARROW = 39;

    const MINUS = 189;
    const EQUAL = 187;

    const ESC_KEY = 27;
    const P_KEY = 80;

    const toggleNoteMap = {
      83: 0,  // s
      68: 1,  // d
      70: 2,  // f
      32: 3,  // space
      74: 4,  // j
      75: 5,  // k
      76: 6   // l
    };

    if (this.props.inPlayback) {
      if (e.keyCode === ESC_KEY || e.keyCode === P_KEY) {
        this.props.flux.getActions('playback').exitPlayback();
      }

    } else {
      if (e.keyCode === UP_ARROW) {
        // TODO: Check for max offset

        this.setState((state) => ({
          offset: state.offset + this.getScrollResolution()
        }));

      } else if (e.keyCode === DOWN_ARROW) {
        const nextOffset = this.state.offset - this.getScrollResolution();

        if (nextOffset >= 0) {
          this.setState({
            offset: nextOffset
          });
        }

      } else if (e.keyCode === PG_UP) {
        // TODO: Check for max offset

        this.setState((state) => ({
          offset: state.offset + (24 * 4)
        }));

      } else if (e.keyCode === PG_DOWN) {
        const nextOffset = this.state.offset - (24 * 4);

        if (nextOffset >= 0) {
          this.setState({
            offset: nextOffset
          });
        }

      } else if (e.keyCode === MINUS) {
        this.setState({
          beatSpacing: this.state.beatSpacing - 40
        });

      } else if (e.keyCode === EQUAL) {
        this.setState({
          beatSpacing: this.state.beatSpacing + 40
        });

      } else if (e.keyCode === LEFT_ARROW) {
        this.handleUpdateScrollResolution(false);

      } else if (e.keyCode === RIGHT_ARROW) {
        this.handleUpdateScrollResolution(true);

      } else if (e.keyCode === P_KEY) {
        this.props.flux.getActions('playback').enterPlayback(this.state.offset, this.props.bpm);

      } else if (toggleNoteMap[e.keyCode] !== undefined) {
        this.handleToggleNote(toggleNoteMap[e.keyCode]);

      } else {
        console.log(e.keyCode);
      }
    }
  }

  renderChart() {
    return (
      <Chart
        numMeasures={this.getNumMeasures()}
        notes={this.props.notes}

        scrollResolution={this.getScrollResolution()}
        offset={this.getOffset()}
        beatSpacing={this.state.beatSpacing} />
    );
  }

  renderLoaded() {
    return (

 this.handleKeyPress(e)} tabIndex="1">
          {this.renderChart()}

        <AudioPlayback playing={this.props.inPlayback} playbackOffset={this.state.offset}
          audioData={this.props.audioData} bpm={this.props.bpm} ctx={this.props.audioCtx} />
        <SaveLoadForm flux={this.props.flux} />
        FPS: {this.props.playbackFps}

    );
  }

  render() {
    return (

        {this.props.audioData ? this.renderLoaded() : null}

    );
  }
}

class EditorOuter extends React.Component {
  componentWillMount() {
    const idx = this.props.params.songIdx;
    this.props.flux.getActions('editor').loadSong(idx);
    this.props.flux.getActions('audio').loadAudio(idx);
  }

  render() {
    return (
      <FluxComponent flux={this.props.flux} connectToStores={{
        editor: (store) => ({
          notes: store.state.notes,
          bpm: store.state.bpm
        }),

        playback: (store) => ({
          inPlayback: store.state.inPlayback,
          playbackOffset: store.state.playbackOffset,
          playbackFps: store.state.playbackFps
        }),

        audio: (store) => ({
          audioData: store.state.audioData,
          audioCtx: store.state.ctx
        })
      }}>
        <Editor />
      </FluxComponent>
    );
  }
}

export default EditorOuter;
<commit_msg>Use react-hotkeys for key mappings<commit_after>import React from 'react';
import FluxComponent from 'flummox/component';
import {HotKeys} from 'react-hotkeys';

import AudioPlayback from './components/AudioPlayback';
import AudioPicker from './components/AudioPicker';
import SaveLoadForm from './components/SaveLoadForm';
import Chart from './components/Chart';

const resolutions = [24, 12, 8, 6, 4, 3];

class Editor extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      offset: 0,
      scrollResolutionIdx: 0,
      beatSpacing: 80
    };
  }

  getKeyMap() {
    if (this.props.inPlayback) {
      return {
        'exitPlayback': ['p', 'esc']
      };
    } else {
      return {
        'scrollUp': ['up', 'shift+k'],
        'scrollDown': ['down', 'shift+j'],
        'measureUp': 'pageup',
        'measureDown': 'pagedown',
        'zoomOut': '-',
        'zoomIn': '=',
        'scrollResDown': 'left',
        'scrollResUp': 'right',
        'enterPlayback': 'p',
        'toggleNote': ['s', 'd', 'f', 'space', 'j', 'k', 'l']
      };
    }
  }

  getHandlers() {
    return {
      scrollUp: (e) => {
        e.preventDefault();

        // TODO: Check for max offset

        this.setState((state) => ({
          offset: state.offset + this.getScrollResolution()
        }));
      },

      scrollDown: (e) => {
        e.preventDefault();

        const nextOffset = this.state.offset - this.getScrollResolution();

        if (nextOffset >= 0) {
          this.setState({
            offset: nextOffset
          });
        }
      },

      measureUp: (e) => {
        e.preventDefault();

        // TODO: Check for max offset

        this.setState((state) => ({
          offset: state.offset + (24 * 4)
        }));
      },

      measureDown: (e) => {
        e.preventDefault();

        const nextOffset = this.state.offset - (24 * 4);

        if (nextOffset >= 0) {
          this.setState({
            offset: nextOffset
          });
        }
      },

      zoomOut: () => {
        this.setState({
          beatSpacing: this.state.beatSpacing - 40
        });
      },

      zoomIn: () => {
        this.setState({
          beatSpacing: this.state.beatSpacing + 40
        });
      },

      scrollResUp: () => {
        this.handleUpdateScrollResolution(true);
      },

      scrollResDown: () => {
        this.handleUpdateScrollResolution(false);
      },

      enterPlayback: () => {
        this.props.flux.getActions('playback').enterPlayback(this.state.offset, this.props.bpm);
      },

      exitPlayback: () => {
        this.props.flux.getActions('playback').exitPlayback();
      },

      toggleNote: (e, key) => {
        const colMap = {
          's': 0,
          'd': 1,
          'f': 2,
          'space': 3,
          'j': 4,
          'k': 5,
          'l': 6
        };

        const col = colMap[key];

        this.handleToggleNote(col);
      }
    };
  }

  getOffset() {
    if (this.props.inPlayback) {
      return this.props.playbackOffset;
    } else {
      return this.state.offset;
    }
  }

  getScrollResolution() {
    return resolutions[this.state.scrollResolutionIdx];
  }

  getNumMeasures() {
    const len = this.props.flux.getStore('audio').getLength();
    return this.props.flux.getStore('editor').getNumMeasures(len);
  }

  handleToggleNote(column) {
    this.props.flux.getActions('editor').toggleNote(this.state.offset, column);
  }

  handleUpdateScrollResolution(increase) {
    const inc = increase ? 1 : -1;

    const nextResIdx = this.state.scrollResolutionIdx + inc;

    if (nextResIdx < 0 || nextResIdx >= resolutions.length) {
      return;
    }

    const nextRes = resolutions[nextResIdx];

    // Snap to nearest note in resolution if resolution changes
    // i.e. if you're on an 8th and drop 8ths to 4ths, snap to previous 4th
    //      if you're on an 8th and drop 16ths to 8ths, stay on the same

    let nextOffset = this.state.offset - (this.state.offset % nextRes);

    this.setState({
      scrollResolutionIdx: nextResIdx,
      offset: nextOffset
    });
  }

  renderChart() {
    return (
      <Chart
        numMeasures={this.getNumMeasures()}
        notes={this.props.notes}

        scrollResolution={this.getScrollResolution()}
        offset={this.getOffset()}
        beatSpacing={this.state.beatSpacing} />
    );
  }

  renderLoaded() {
    return (

        <HotKeys handlers={this.getHandlers()} keyMap={this.getKeyMap()}>

            {this.renderChart()}

        </HotKeys>

        <SaveLoadForm flux={this.props.flux} />
        FPS: {this.props.playbackFps}

        <AudioPlayback playing={this.props.inPlayback} playbackOffset={this.state.offset}
          audioData={this.props.audioData} bpm={this.props.bpm} ctx={this.props.audioCtx} />

    );
  }

  render() {
    return (

        {this.props.audioData ? this.renderLoaded() : null}

    );
  }
}

class EditorOuter extends React.Component {
  componentWillMount() {
    const idx = this.props.params.songIdx;
    this.props.flux.getActions('editor').loadSong(idx);
    this.props.flux.getActions('audio').loadAudio(idx);
  }

  render() {
    return (
      <FluxComponent flux={this.props.flux} connectToStores={{
        editor: (store) => ({
          notes: store.state.notes,
          bpm: store.state.bpm
        }),

        playback: (store) => ({
          inPlayback: store.state.inPlayback,
          playbackOffset: store.state.playbackOffset,
          playbackFps: store.state.playbackFps
        }),

        audio: (store) => ({
          audioData: store.state.audioData,
          audioCtx: store.state.ctx
        })
      }}>
        <Editor />
      </FluxComponent>
    );
  }
}

export default EditorOuter;
<|endoftext|>


<commit_before>// EMAIL NEWSLETTER SIGNUPS

const changeEmailContainerBackground = elem => {
	// If background image is so complex that email signup form needs a dark background instead of a transparent background...
	let visible = false
	for (let i = 0; i < elem.childNodes.length; i++) {
		// Element with class `email container background` has conditional visibility in Webflow, so Webflow tags a new class called `w-condition-invisible` if Collection determines it invisible
		visible = elem.childNodes[i].className.includes('w-condition-invisible')
		if (visible) {
			break
		}
	}
	const color = !visible
		? '#fff'
		: '#333'
	const mode = !visible
		? 'add'
		: 'remove'
	for (let i = 0; i < elem.childNodes.length; i++) {
		// Elements are contained inside `email-form` class...
		if (elem.childNodes[i].className === 'email-form') {
      console.log(elem.childNodes[i])
			for (let j = 0; j < elem.childNodes[i].length; j++) {
				const element = elem.childNodes[i][j]
				console.log(element)
				// ...then make the email signup box title white instead of charcoal.
				if (element.className.includes('title small') || element.className.includes('button transparent')) {
					element.style.color = color
				}
				else if (element.className.includes('input') || element.className.includes('textarea')) {
					if (mode === 'add') {
						element.classList.add('white-placeholder')
					}
					else {
						element.classList.remove('white-placeholder')
					}
				}
			}
		}
	}

	}

// 1. Define all input fields
const formNames = [
	'hero',
]
const formFields = [
	'first_name',
	'last_name',
	'email',
	'verification'
]

// 2. Only once all fields are entered, enable button.
const verifyEmailSignup = e => {
	formNames.forEach(inputCategory => {
		// If change was made in a certain email signup category...
		if (e.target.id.includes(inputCategory)) {
			// ...check to see if every field has been filled out correctly...
			const complete = formFields.every(inputField => {
				const field = document.getElementById(inputCategory + '_' + inputField)
				if (inputField === 'first_name' || inputField === 'last_name') {
					// Prevent ALL CAPS
					field.value = field.value.toLowerCase()
					// Title case
					field.value = field.value.charAt(0).toUpperCase() + field.value.slice(1)
					// No empty spaces
					if (field.value.includes(' ')) {
						field.value = field.value.replace(' ', '')
					}
				}
				const emailRegex = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}/igm
				// return true if all fields have been filled out
				return (
					((inputField === 'first_name' || inputField === 'last_name') && field.value.length > 1)
					|| (inputField === 'email' && field.value.length > 4 && emailRegex.test(field.value))
				)
			})
			const showAlert = formFields.every(inputField => {
				const field = document.getElementById(inputCategory + '_' + inputField)
				return (
					// Only show alert once every field has been touched...
					field.value.length > 0
				)
			})
			// ...and if so, change button class to active, and submit form.
			const buttonField = document.getElementById(inputCategory + '_button')
			const alertField = document.getElementById(inputCategory + '_alert')
			// Only show alert if all fields have been filled out somewhat, but not yet validated
			if (showAlert) {
				alertField.classList.remove('hidden')
			}
			else {
				alertField.classList.add('hidden')
			}
			if (complete && !showAlert) {
				buttonField.classList.remove('disabled')
				const formField = document.getElementById(inputCategory + '_form')
				formField.action = 'https://app.getresponse.com/add_subscriber.html'
				buttonField.addEventListener('click', () => formField.submit())
			}
			// ...otherwise, disable form.
			else {
				buttonField.classList.add('disabled')
				const formField = document.getElementById(inputCategory + '_form')
				formField.action = ''
				buttonField.removeEventListener('click', () => null)
			}
		}
	})
}

// 3. Assign enable button function to all relevant fields, and trigger submit if user presses enter
formNames.forEach(inputCategory => {
	formFields.forEach(inputField => {
		const field = document.getElementById(inputCategory + '_' + inputField)
		if (field) {
			field.oninput = verifyEmailSignup
			field.addEventListener('keypress', e => {
				const key = e.which || e.keyCode
				// If `enter` key is pressed, attempt to submit form
				if (key === 13) {
					const buttonField = document.getElementById(inputCategory + '_button')
					buttonField.click()
				}
			})
		}
	})
})<commit_msg>Reg form modal display.<commit_after>// EMAIL NEWSLETTER SIGNUPS

const changeEmailContainerBackground = elem => {
	// If background image is so complex that email signup form needs a dark background instead of a transparent background...
	let visible = false
	for (let i = 0; i < elem.children.length; i++) {
		// Element with class `email container background` has conditional visibility in Webflow, so Webflow tags a new class called `w-condition-invisible` if Collection determines it invisible
		visible = elem.children[i].className.includes('w-condition-invisible')
		if (visible) {
			break
		}
	}
	const color = !visible
		? '#fff'
		: '#333'
	const mode = !visible
		? 'add'
		: 'remove'
	for (let i = 0; i < elem.children.length; i++) {
		// Elements are contained inside `email-form` class...
		if (elem.children[i].className === 'email-form') {
      console.log(elem.children[i])
			for (let j = 0; j < elem.children[i].length; j++) {
				const element = elem.children[i][j]
				console.log(element)
				// ...then make the email signup box title white instead of charcoal.
				if (element.className.includes('title small') || element.className.includes('button transparent')) {
					element.style.color = color
				}
				else if (element.className.includes('input') || element.className.includes('textarea')) {
					if (mode === 'add') {
						element.classList.add('white-placeholder')
					}
					else {
						element.classList.remove('white-placeholder')
					}
				}
			}
		}
	}

	}

// 1. Define all input fields
const formNames = [
	'hero',
]
const formFields = [
	'first_name',
	'last_name',
	'email',
	'verification'
]

// 2. Only once all fields are entered, enable button.
const verifyEmailSignup = e => {
	formNames.forEach(inputCategory => {
		// If change was made in a certain email signup category...
		if (e.target.id.includes(inputCategory)) {
			// ...check to see if every field has been filled out correctly...
			const complete = formFields.every(inputField => {
				const field = document.getElementById(inputCategory + '_' + inputField)
				if (inputField === 'first_name' || inputField === 'last_name') {
					// Prevent ALL CAPS
					field.value = field.value.toLowerCase()
					// Title case
					field.value = field.value.charAt(0).toUpperCase() + field.value.slice(1)
					// No empty spaces
					if (field.value.includes(' ')) {
						field.value = field.value.replace(' ', '')
					}
				}
				const emailRegex = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}/igm
				// return true if all fields have been filled out
				return (
					((inputField === 'first_name' || inputField === 'last_name') && field.value.length > 1)
					|| (inputField === 'email' && field.value.length > 4 && emailRegex.test(field.value))
				)
			})
			const showAlert = formFields.every(inputField => {
				const field = document.getElementById(inputCategory + '_' + inputField)
				return (
					// Only show alert once every field has been touched...
					field.value.length > 0
				)
			})
			// ...and if so, change button class to active, and submit form.
			const buttonField = document.getElementById(inputCategory + '_button')
			const alertField = document.getElementById(inputCategory + '_alert')
			// Only show alert if all fields have been filled out somewhat, but not yet validated
			if (showAlert) {
				alertField.classList.remove('hidden')
			}
			else {
				alertField.classList.add('hidden')
			}
			if (complete && !showAlert) {
				buttonField.classList.remove('disabled')
				const formField = document.getElementById(inputCategory + '_form')
				formField.action = 'https://app.getresponse.com/add_subscriber.html'
				buttonField.addEventListener('click', () => formField.submit())
			}
			// ...otherwise, disable form.
			else {
				buttonField.classList.add('disabled')
				const formField = document.getElementById(inputCategory + '_form')
				formField.action = ''
				buttonField.removeEventListener('click', () => null)
			}
		}
	})
}

// 3. Assign enable button function to all relevant fields, and trigger submit if user presses enter
formNames.forEach(inputCategory => {
	formFields.forEach(inputField => {
		const field = document.getElementById(inputCategory + '_' + inputField)
		if (field) {
			field.oninput = verifyEmailSignup
			field.addEventListener('keypress', e => {
				const key = e.which || e.keyCode
				// If `enter` key is pressed, attempt to submit form
				if (key === 13) {
					const buttonField = document.getElementById(inputCategory + '_button')
					buttonField.click()
				}
			})
		}
	})
})<|endoftext|>


<commit_before>function createUi() {
    return new SimpleUi();
}

function createDefaultLevel(ui) {

    var points = [
        [0, 0],
        [1, 0],
        [1, 1],
        [1, 2],
        [2, 2],
        [3, 2],
        [3, 3],
        [3, 4],
        [4, 4],
        [5, 4],
        [5, 3],
        [5, 2],
        [6, 2],
        [7, 2],
        [8, 2],
        [9, 2]
    ];

	var nodes = generateNodes(points);
	var map = new Map(nodes, ui);
	var van = new Van(nodes[0], nodes[1], ui);
	return new Level(map, van, nodes[nodes.length - 1], ui);
}

function generateNodes(points){
	var previousNode = null;
	var nodes = [];
	for (var i = 0; i < points.length; i++) {
	      var p = points[i];
	      var coordinate = new Coordinate(p[0], p[1]);
	      var node = new Node(coordinate);
	      if (previousNode) {
	          node.addConnectedNodeWithBacklink(previousNode);
	      }
	      previousNode = node;
	      nodes.push(node);
	}
	return nodes;
}

/**
  * Generates a random road given a starting point and seed from the range <0, 1>,
  * where 1 creates a completely straight road and 0 does not influence the way
  * the next road element is chosen at all.
  */
function generateRandomPathPoints(current, seed) {
	var points = [];
	var visited = initialiseVisited();
	var possibleNext = null;
	var orientation = current[1] == 0 ? -1 : 2;
	var possibleStaight = null;

	points.push(current);
	visited[current[0]][current[1]] = true;
	current = getNextBasedOnOrientation(current, orientation);

	while (!isOutOfBounds(current)) {

		visited[current[0]][current[1]] = true;
		possibleNext = getPossibleNextMoves(current, visited);
		possibleStraight = getNextBasedOnOrientation(current, orientation);

		if (Math.random() < seed && possibleStraight != -1
			&& isFree(possibleStraight, visited)) {
			points.push(current);
			current = possibleStraight;
			console.debug(current);

		} else {

			if (possibleNext.length == 0) {
				if(isOnBorder(current)) {
					points.push(current);
					return points;
				}
				current = points.pop();

			} else {

				var decision = Math.floor((Math.random() * possibleNext.length));
				points.push(current);
				next = possibleNext[decision];
				orientation =  (2 * (next[0] - current[0]) + (next[1] - current[1]));
				current = possibleNext[decision];
			}
		}
	}
	console.debug("blah");
	for (var i = 0; i < points.length; i++) {
		console.debug(points[i]);
	}
	console.debug("blah2");
	return points;

	/*						*(1, 0) 1
	 *
	 *      *(-1,0) -2     	x(0, 0)      	*(1, 0) 2
	 *
	 *						*(-1, 0) -1
	 *
	 * Returns next point in the same orientation or -1 if it would go out of bounds.
	 */
	function getNextBasedOnOrientation(point, orientation) {
		var result = null;
		switch(orientation) {
			case 1:
				result = [point[0], point[1] + 1];
				break;
			case -1:
				result = [point[0], point[1] - 1];
				break;
			case 2:
				result = [point[0] + 1, point[1]];
				break;
			case -2:
				result = [point[0] - 1, point[1]];
				break;
		}
		return isOutOfBounds(result) ? -1 : result;
	}

	function isOutOfBounds(point) {
		return point[0] < 0 || point[0] >= GRID_WIDTH || point[1] < 0 || point[1] >= GRID_HEIGHT;
	}

	function isOnBorder(point) {
		return point[0] == 0 || point[0] == GRID_WIDTH || point[1] == 0 || point[1] == GRID_HEIGHT;
	}

	function isFree(point, visited) {
		return !visited[point[0]][point[1]];
	}

	function getPossibleNextMoves(point, visited) {
		var possible = [];
		var possiblePoint = null;
		var considered = [[point[0], point[1] + 1], [point[0] + 1, point[1]],
			[point[0] - 1, point[1]], [point[0], point[1] - 1]];
		for (var i= 0; i < 4; i++) {
			possiblePoint = considered[i];
			if(!isOutOfBounds(possiblePoint) && isFree(possiblePoint, visited)) {
				possible.push(possiblePoint);
			}
		}
		return possible;
	}

	function initialiseVisited() {
		var visited = new Array(GRID_WIDTH);
		for (var i = 0; i < GRID_WIDTH; i++) {
			visited[i] = new Array(GRID_HEIGHT);
		}
		return visited;
	}
}

function defaultProgram(level) {
	  var program = new Program(
	          [TURN_LEFT,
	              FORWARD,
	              TURN_RIGHT,
	              FORWARD,
	              TURN_LEFT,
	              FORWARD,
	              TURN_RIGHT,
	              FORWARD,
	              TURN_RIGHT,
	              FORWARD,
	              TURN_LEFT,
	              FORWARD,
	              FORWARD,
	              FORWARD
	          ]);

	  level.play(program);
}

function trackDevelopment(level) {
	var program = new Program([]);

	$('#moveForward').click(function() {
		program.instructions.push(FORWARD);
    });

    $('#turnLeft').click(function() {
    	program.instructions.push(TURN_LEFT);
    });

    $('#turnRight').click(function() {
		program.instructions.push(TURN_RIGHT);
	});

    $('#play').click(function() {
    	level.play(program);
	});

	$('#randomRoad').click(function() {
		var ui = createUi();
		var points = generateRandomPathPoints([0,4], 0);
		var nodes = generateNodes(points);
		var van = new Van(nodes[0], nodes[1], ui);
		var map = new Map(nodes, ui);
	})
}

$(function() {
    'use strict';

    var ui = createUi();

    var level = createDefaultLevel(ui);

    $('#runDefaultProgram').click(function() {
    	defaultProgram(level);
	});

    trackDevelopment(level);
});
<commit_msg>Added the length parameter. Will probably be useful for simple levels together with the high seed.<commit_after>function createUi() {
    return new SimpleUi();
}

function createDefaultLevel(ui) {

    var points = [
        [0, 0],
        [1, 0],
        [1, 1],
        [1, 2],
        [2, 2],
        [3, 2],
        [3, 3],
        [3, 4],
        [4, 4],
        [5, 4],
        [5, 3],
        [5, 2],
        [6, 2],
        [7, 2],
        [8, 2],
        [9, 2]
    ];

	var nodes = generateNodes(points);
	var map = new Map(nodes, ui);
	var van = new Van(nodes[0], nodes[1], ui);
	return new Level(map, van, nodes[nodes.length - 1], ui);
}

function generateNodes(points){
	var previousNode = null;
	var nodes = [];
	for (var i = 0; i < points.length; i++) {
	      var p = points[i];
	      var coordinate = new Coordinate(p[0], p[1]);
	      var node = new Node(coordinate);
	      if (previousNode) {
	          node.addConnectedNodeWithBacklink(previousNode);
	      }
	      previousNode = node;
	      nodes.push(node);
	}
	return nodes;
}

/**
  * Generates a random road given a starting point and seed and length.
  * Seed - a number from the range <0, 1>, where 1 creates a completely straight road
  * and 0 does not influence the way the next road element is chosen at all.
  * Length - optional argument limiting the length of the path.
  */
function generateRandomPathPoints(current, seed, length) {
	var points = [];
	var visited = initialiseVisited();
	var possibleNext = null;
	var orientation = current[1] == 0 ? -1 : 2;
	var possibleStaight = null;
	length = length == undefined ? Number.POSITIVE_INFINITY : length;

	points.push(current);
	visited[current[0]][current[1]] = true;
	current = getNextBasedOnOrientation(current, orientation);

	while (!isOutOfBounds(current) && points.length < length) {

		visited[current[0]][current[1]] = true;
		possibleNext = getPossibleNextMoves(current, visited);
		possibleStraight = getNextBasedOnOrientation(current, orientation);

		if (Math.random() < seed && possibleStraight != -1
			&& isFree(possibleStraight, visited)) {
			points.push(current);
			current = possibleStraight;
			console.debug(current);

		} else {

			if (possibleNext.length == 0) {
				if(isOnBorder(current)) {
					points.push(current);
					return points;
				}
				current = points.pop();

			} else {

				var decision = Math.floor((Math.random() * possibleNext.length));
				points.push(current);
				next = possibleNext[decision];
				orientation =  (2 * (next[0] - current[0]) + (next[1] - current[1]));
				current = possibleNext[decision];
			}
		}
	}
	console.debug("blah");
	for (var i = 0; i < points.length; i++) {
		console.debug(points[i]);
	}
	console.debug("blah2");
	return points;

	/*						*(1, 0) 1
	 *
	 *      *(-1,0) -2     	x(0, 0)      	*(1, 0) 2
	 *
	 *						*(-1, 0) -1
	 *
	 * Returns next point in the same orientation or -1 if it would go out of bounds.
	 */
	function getNextBasedOnOrientation(point, orientation) {
		var result = null;
		switch(orientation) {
			case 1:
				result = [point[0], point[1] + 1];
				break;
			case -1:
				result = [point[0], point[1] - 1];
				break;
			case 2:
				result = [point[0] + 1, point[1]];
				break;
			case -2:
				result = [point[0] - 1, point[1]];
				break;
		}
		return isOutOfBounds(result) ? -1 : result;
	}

	function isOutOfBounds(point) {
		return point[0] < 0 || point[0] >= GRID_WIDTH || point[1] < 0 || point[1] >= GRID_HEIGHT;
	}

	function isOnBorder(point) {
		return point[0] == 0 || point[0] == GRID_WIDTH || point[1] == 0 || point[1] == GRID_HEIGHT;
	}

	function isFree(point, visited) {
		return !visited[point[0]][point[1]];
	}

	function getPossibleNextMoves(point, visited) {
		var possible = [];
		var possiblePoint = null;
		var considered = [[point[0], point[1] + 1], [point[0] + 1, point[1]],
			[point[0] - 1, point[1]], [point[0], point[1] - 1]];
		for (var i= 0; i < 4; i++) {
			possiblePoint = considered[i];
			if(!isOutOfBounds(possiblePoint) && isFree(possiblePoint, visited)) {
				possible.push(possiblePoint);
			}
		}
		return possible;
	}

	function initialiseVisited() {
		var visited = new Array(GRID_WIDTH);
		for (var i = 0; i < GRID_WIDTH; i++) {
			visited[i] = new Array(GRID_HEIGHT);
		}
		return visited;
	}
}

function defaultProgram(level) {
	  var program = new Program(
	          [TURN_LEFT,
	              FORWARD,
	              TURN_RIGHT,
	              FORWARD,
	              TURN_LEFT,
	              FORWARD,
	              TURN_RIGHT,
	              FORWARD,
	              TURN_RIGHT,
	              FORWARD,
	              TURN_LEFT,
	              FORWARD,
	              FORWARD,
	              FORWARD
	          ]);

	  level.play(program);
}

function trackDevelopment(level) {
	var program = new Program([]);

	$('#moveForward').click(function() {
		program.instructions.push(FORWARD);
    });

    $('#turnLeft').click(function() {
    	program.instructions.push(TURN_LEFT);
    });

    $('#turnRight').click(function() {
		program.instructions.push(TURN_RIGHT);
	});

    $('#play').click(function() {
    	level.play(program);
	});

	$('#randomRoad').click(function() {
		var ui = createUi();
		var points = generateRandomPathPoints([0,4], 0.5, 13);
		var nodes = generateNodes(points);
		var van = new Van(nodes[0], nodes[1], ui);
		var map = new Map(nodes, ui);
	})
}

$(function() {
    'use strict';

    var ui = createUi();

    var level = createDefaultLevel(ui);

    $('#runDefaultProgram').click(function() {
    	defaultProgram(level);
	});

    trackDevelopment(level);
});
<|endoftext|>


<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2013 David Evans, Killian Koenig
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// The main TANK file that provides access to the core functionality of the engine.
// This includes creating and manipulating entities, registering components, and dispatching events.
(function (TANK)
{
  "use strict";

  // ### Enable/Disable error messages
  TANK.errorsEnabled = true;

  // ### Enable/Disable warning messages
  TANK.warningsEnabled = true;

  // ### Enable/Disable logging messages
  TANK.logsEnabled = true;

  // ### Create an Entity
  // Creates a new `Entity` and returns it. The parameters to the function are passed
  // directly to `Entity.addComponents` after construction.
  //
  // - `return`: A new `Entity`.
  TANK.createEntity = function ()
  {
    var entity = new TANK.Entity(-1);
    return entity.addComponents.apply(entity, arguments);
  };

  // ### Create Entity from prefab
  // Creates a new `Entity` using a given prefab.
  //
  // - `prefabName`: Name of the prefab to clone.
  // - `return`: A new `Entity`.
  TANK.createEntityFromPrefab = function (prefabName)
  {
    var prefab = TANK.getPrefab(prefabName);
    if (!prefab)
    {
      TANK.warning("Could not find a prefab named " + prefabName);
      return;
    }

    var entity = TANK.createEntity();
    var component;
    var componentData;
    var componentName, propertyName;
    for (componentName in prefab)
    {
      if (prefab.hasOwnProperty(componentName))
      {
        entity.addComponent(componentName);

        // Copy over fields from prefab into new component
        component = entity[componentName];
        componentData = prefab[componentName];
        for (propertyName in componentData)
        {
          if (componentData.hasOwnProperty(propertyName))
          {
            component[propertyName] = componentData[propertyName];
          }
        }
      }
    }

    return entity;
  };

  // ### Add an entity to the world.
  // Adds the given entity to the world, which will initialize all of its
  // components.
  //
  // - `object`: The entity to add the world.
  // - `name`: (optional) A unique name to track the entity by.
  // - `return`: The initialized entity.
  TANK.addEntity = function (object, name)
  {
    if (object.id !== -1)
    {
      TANK.error("Attempting to add a Entity twice");
      return object;
    }

    object.id = this._currentID++;

    if (object.name === null)
    {
      object.name = "Entity " + object.id;
    }

    // If a name was specified, track it by the name
    if (name)
    {
      object.name = name;
      if (TANK._objectsNamed[name])
      {
        TANK.error("An Entity named " + name + " already exists");
        return object;
      }
      TANK._objectsNamed[name] = object;
    }

    // Track the object by its id
    TANK._objects[object.id] = object;

    // Track the components by their interfaces
    var n, c, componentDef, i;
    for (n in object._components)
    {
      c = object._components[n];
      componentDef = TANK._registeredComponents[n];
      for (i = 0; i < componentDef._interfaces.length; ++i)
      {
        // Get the list of components with this interface
        var componentList = TANK._interfaceComponents[componentDef._interfaces[i]];
        if (!componentList)
        {
          componentList = {};
          TANK._interfaceComponents[componentDef._interfaces[i]] = componentList;
        }

        componentList[object.name + "." + componentDef.name] = c;
      }
    }

    // Initialize each component
    for (n in object._components)
    {
      c = object._components[n];
      c.initialize();
    }

    for (n in object._components)
    {
      c = object._components[n];
      TANK.dispatchEvent("OnComponentInitialized", c);
    }

    object._initialized = true;

    return object;
  };

  // ### Get an entity
  //
  // - `idOrName`: Either the id of the entity, or its unique name
  // - `return`: The requested `Entity` or `undefined`
  TANK.getEntity = function (idOrName)
  {
    if (typeof idOrName === "string")
    {
      return TANK._objectsNamed[idOrName];
    }

    if (typeof idOrName === "number")
    {
      return TANK._objects[idOrName];
    }

    TANK.error("Attemping to get an Entity with neither a string name nor an id number: " + idOrName);
  };

  // ### Register an object prefab
  // Use this to define an entity with a set of components that
  // can be instantiated later, like a blueprint.
  //
  // - `name`: The name of the prefab to store it under.
  // - `data`: A JSON object describing the components the prefab should contain,
  // with the following format:
  //
  //       {
  //         "Pos2D": { x: 0, y: 42 },
  //         "Velocity": {},
  //         "Collider": { width: 5, height: 5 },
  //       }
  TANK.addPrefab = function (name, data)
  {
    TANK._prefabs[name] = data;
  };

  // ### Remove an object
  // Schedules the given object to be deleted on the next frame.
  // Will cause `destruct` to be called on all components of the object before it is deleted.
  //
  // `id`: The id of the object. (`Entity.id`)
  TANK.removeEntity = function (arg)
  {
    if (typeof arg === "string" || typeof arg === "number")
    {
      var entity = TANK.getEntity(arg);
      if (entity)
      {
        TANK._objectsDeleted.push(TANK.getEntity(arg));
      }
      else
      {
        TANK.error("Attempting to remove Entity " + arg + " which doesn't exist.");
      }
    }
    else if (arg instanceof TANK.Entity)
    {
      TANK._objectsDeleted.push(arg);
    }
    else
    {
      TANK.error("Attemping to remove an Entity with neither a string name, id number, or Entity reference: " + arg);
    }
  };

  // ### Remove all objects
  // Equivalent to calling `removeEntity` on each objects.
  TANK.removeAllEntities = function ()
  {
    var i;
    for (i in TANK._objects)
    {
      if (TANK._objects.hasOwnProperty(i))
      {
        TANK.removeEntity(parseInt(i, 10));
      }
    }
  };

  // ### Register an object prefab
  // Use this to define an entity with a set of components that
  // can be instantiated later, like a blueprint.
  //
  // - `name`: The name of the prefab to store it under.
  // - `data`: A JSON object describing the components the prefab should contain,
  // with the following format:
  //
  //       {
  //         "Pos2D": { x: 0, y: 42 },
  //         "Velocity": {},
  //         "Collider": { width: 5, height: 5 },
  //       }
  TANK.addPrefab = function (name, data)
  {
    TANK._prefabs[name] = data;
  };

  // ### Get a prefab object
  //
  // - `name`: The name of the prefab to find.
  // - `return`: A prefab JSON object.
  TANK.getPrefab = function (name)
  {
    return TANK._prefabs[name];
  };

  // ### Register a new component type
  // Creates a new `Component` instance which defines a blueprint
  // for a type of component.
  //
  // - `componentName`: The name of the component type to register. This must be a valid identifier.
  // - `return`: A new instance of type `Component`.
  TANK.registerComponent = function (componentName)
  {
    // Warn about components with invalid identifiers
    if (componentName[0] >= 0 && componentName[0] <= 9 || componentName.search(" ") >= 0)
    {
      TANK.error(componentName + " is an invalid identifier and won't be accessible without [] operator");
      return;
    }

    var c = new TANK.Component(componentName);
    TANK._registeredComponents[componentName] = c;
    return c;
  };

  // ### Add a component to the engine
  // Components added to the engine are "global" and not
  // affected by spaces. These are commonly used to add systems
  // that contain global state, such as a graphics context.
  //
  // - `componentName`: The name of the component to add to the engine.
  TANK.addComponent = function (componentName)
  {
    // Check if we have this component already
    if (TANK[componentName])
    {
      return;
    }

    // Create engine entity if it doesn't exist
    if (!TANK._engineEntity)
    {
      TANK._engineEntity = TANK.createEntity();
    }

    TANK._engineEntity.addComponent(componentName);
    TANK[componentName] = TANK._engineEntity[componentName];

    // Only run initialize if the engine is already running
    // because all components are initialized on `TANK.start()`
    if (TANK._running)
    {
      TANK[componentName].initialize();
    }

    return TANK;
  };

  // ### Add multiple components to the engine
  // Components can be given as a string of comma seperated values,
  // a list of strings, or some combination of the above.
  // e.g., `TANK.addComponents("Pos2D, Velocity", "Image", "Collider");`
  TANK.addComponents = function ()
  {
    var i, j, arg;
    for (i = 0; i < arguments.length; ++i)
    {
      arg = arguments[i];
      arg = arg.replace(/\s/g, "");
      var components = arg.split(",");

      for (j = 0; j < components.length; ++j)
      {
        TANK.addComponent(components[j]);
      }
    }

    return this;
  };

  // ### Remove a component from the engine
  //
  // - `componentName`: The name of the component to remove.
  TANK.removeComponent = function (componentName)
  {
    var c = TANK[componentName];
    if (!c)
    {
      return;
    }

    if (TANK._engineEntity)
    {
      TANK._engineEntity.removeComponent(componentName);
      delete TANK[componentName];
    }
  };

  // ### Find components with a given interface
  // Gets all component instances that implement a particular interface.
  //
  // - `interfaceName`: Name of the interface that returned components should implement.
  // - `return`: An array of component instances.
  TANK.getComponentsWithInterface = function (interfaceName)
  {
    return TANK._interfaceComponents[interfaceName];
  };

  // ### Send out an event
  // Takes any number of arguments after event name.
  //
  // `eventName` - Name of the event to trigger.
  TANK.dispatchEvent = function (eventName)
  {
    // Get array of listeners
    var listeners = TANK._events[eventName];
    if (!listeners)
      return;

    // Construct arguments
    var message_args = [];
    for (var i = 1; i < arguments.length; ++i)
      message_args.push(arguments[i]);

    // Invoke the message on each listener
    var func, thisObj;
    for (var i in listeners)
    {
      func = listeners[i].func;
      thisObj = listeners[i].self;
      if (func)
        func.apply(thisObj, message_args);
      else
        TANK.log(thisObj + " is listening for " + eventName + " but does not implement a method of the same name");
    }
  };

  // ### Start the engine main loop
  TANK.start = function ()
  {
    TANK._lastTime = new Date();
    TANK._running = true;

    // Initialize all engine components
    TANK._engineEntity.invoke("initialize");

    update()
  };

  // ### Stop the engine
  TANK.stop = function ()
  {
    TANK._running = false
  };

  // ### Reset the engine
  // This deletes all game objects and resets the state of the engine.
  // Things like prefabs and component definitions are preserved.
  TANK.reset = function ()
  {
    TANK._resetting = true;
    TANK.removeAllEntities();
  };

  // ### Log a message to console
  //
  // `text` - Text to display.
  TANK.log = function (text)
  {
    if (!TANK.logsEnabled)
      return;

    console.log(text);
  };

  // ### Log a warning message to console
  //
  // `text` - Text to display.
  TANK.warn = function (text)
  {
    if (!TANK.warningsEnabled)
      return;

    console.warn(text);
  };

  // ### Log an error message to console
  //
  // `text` - Text to display.
  TANK.error = function (text)
  {
    if (!TANK.errorsEnabled)
      return;

    console.error(text);
  };

  function update()
  {
    // Get dt
    var new_time = new Date();
    var dt = (new_time - TANK._lastTime) / 1000.0;
    if (dt > 0.05)
      dt = 0.05;

    // Delete pending objects
    for (var i in TANK._objectsDeleted)
    {
      var obj = TANK._objectsDeleted[i];
      obj.destruct();
      delete TANK._objects[obj.id];
      delete TANK._objectsNamed[obj.name];
      obj.id = -1;
      obj.name = "Deleted";
    }
    TANK._objectsDeleted = [];

    // If we are resetting the engine, stop updating before the next frame but after
    // we've cleared up the deleted objects
    if (TANK._resetting)
    {
      // Remove all engine components
      for (var i in TANK._engineEntity._components)
        delete TANK[i];
      TANK._engineEntity.destruct();

      TANK._resetting = false;
      TANK._running = false;
      main();
      return;
    }

    // Dispatch enter frame message
    TANK.dispatchEvent("OnEnterFrame", dt);

    // Queue next frame
    if (TANK._running)
      requestAnimFrame(update);
  };

  // Map of objects tracked by the core
  // Key is the id of the object
  TANK._objects = {};

  // Secondary map of objects with name as key
  TANK._objectsNamed = {};

  // List of objects to delete
  TANK._objectsDeleted = [];

  // Map of prefabs with name as key
  TANK._prefabs = {};

  // "Proxy" entity that stores engine components
  TANK._engineEntity = null;

  // Map of current registered component types
  // Key is the name of the component
  TANK._registeredComponents = {};

  // Map of existing component instances sorted by tag names
  // Key is the name of the tag
  TANK._interfaceComponents = {};

  // Map of objects listening for a message
  TANK._events = {};

  // Current ID for game objects
  TANK._currentID = 0;

  // Last update time
  TANK._lastTime = new Date();

  // Whether or not the engine is running
  TANK._running = false;

  // Whether or not the engine is in the resetting state
  TANK._resetting = false;

}(this.TANK = this.TANK ||
{}));<commit_msg>Remove duplicate function<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2013 David Evans, Killian Koenig
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// The main TANK file that provides access to the core functionality of the engine.
// This includes creating and manipulating entities, registering components, and dispatching events.
(function (TANK)
{
  "use strict";

  // ### Enable/Disable error messages
  TANK.errorsEnabled = true;

  // ### Enable/Disable warning messages
  TANK.warningsEnabled = true;

  // ### Enable/Disable logging messages
  TANK.logsEnabled = true;

  // ### Create an Entity
  // Creates a new `Entity` and returns it. The parameters to the function are passed
  // directly to `Entity.addComponents` after construction.
  //
  // - `return`: A new `Entity`.
  TANK.createEntity = function ()
  {
    var entity = new TANK.Entity(-1);
    return entity.addComponents.apply(entity, arguments);
  };

  // ### Create Entity from prefab
  // Creates a new `Entity` using a given prefab.
  //
  // - `prefabName`: Name of the prefab to clone.
  // - `return`: A new `Entity`.
  TANK.createEntityFromPrefab = function (prefabName)
  {
    var prefab = TANK.getPrefab(prefabName);
    if (!prefab)
    {
      TANK.warning("Could not find a prefab named " + prefabName);
      return;
    }

    var entity = TANK.createEntity();
    var component;
    var componentData;
    var componentName, propertyName;
    for (componentName in prefab)
    {
      if (prefab.hasOwnProperty(componentName))
      {
        entity.addComponent(componentName);

        // Copy over fields from prefab into new component
        component = entity[componentName];
        componentData = prefab[componentName];
        for (propertyName in componentData)
        {
          if (componentData.hasOwnProperty(propertyName))
          {
            component[propertyName] = componentData[propertyName];
          }
        }
      }
    }

    return entity;
  };

  // ### Add an entity to the world.
  // Adds the given entity to the world, which will initialize all of its
  // components.
  //
  // - `object`: The entity to add the world.
  // - `name`: (optional) A unique name to track the entity by.
  // - `return`: The initialized entity.
  TANK.addEntity = function (object, name)
  {
    if (object.id !== -1)
    {
      TANK.error("Attempting to add a Entity twice");
      return object;
    }

    object.id = this._currentID++;

    if (object.name === null)
    {
      object.name = "Entity " + object.id;
    }

    // If a name was specified, track it by the name
    if (name)
    {
      object.name = name;
      if (TANK._objectsNamed[name])
      {
        TANK.error("An Entity named " + name + " already exists");
        return object;
      }
      TANK._objectsNamed[name] = object;
    }

    // Track the object by its id
    TANK._objects[object.id] = object;

    // Track the components by their interfaces
    var n, c, componentDef, i;
    for (n in object._components)
    {
      c = object._components[n];
      componentDef = TANK._registeredComponents[n];
      for (i = 0; i < componentDef._interfaces.length; ++i)
      {
        // Get the list of components with this interface
        var componentList = TANK._interfaceComponents[componentDef._interfaces[i]];
        if (!componentList)
        {
          componentList = {};
          TANK._interfaceComponents[componentDef._interfaces[i]] = componentList;
        }

        componentList[object.name + "." + componentDef.name] = c;
      }
    }

    // Initialize each component
    for (n in object._components)
    {
      c = object._components[n];
      c.initialize();
    }

    for (n in object._components)
    {
      c = object._components[n];
      TANK.dispatchEvent("OnComponentInitialized", c);
    }

    object._initialized = true;

    return object;
  };

  // ### Get an entity
  //
  // - `idOrName`: Either the id of the entity, or its unique name
  // - `return`: The requested `Entity` or `undefined`
  TANK.getEntity = function (idOrName)
  {
    if (typeof idOrName === "string")
    {
      return TANK._objectsNamed[idOrName];
    }

    if (typeof idOrName === "number")
    {
      return TANK._objects[idOrName];
    }

    TANK.error("Attemping to get an Entity with neither a string name nor an id number: " + idOrName);
  };

  // ### Remove an object
  // Schedules the given object to be deleted on the next frame.
  // Will cause `destruct` to be called on all components of the object before it is deleted.
  //
  // `id`: The id of the object. (`Entity.id`)
  TANK.removeEntity = function (arg)
  {
    if (typeof arg === "string" || typeof arg === "number")
    {
      var entity = TANK.getEntity(arg);
      if (entity)
      {
        TANK._objectsDeleted.push(TANK.getEntity(arg));
      }
      else
      {
        TANK.error("Attempting to remove Entity " + arg + " which doesn't exist.");
      }
    }
    else if (arg instanceof TANK.Entity)
    {
      TANK._objectsDeleted.push(arg);
    }
    else
    {
      TANK.error("Attemping to remove an Entity with neither a string name, id number, or Entity reference: " + arg);
    }
  };

  // ### Remove all objects
  // Equivalent to calling `removeEntity` on each objects.
  TANK.removeAllEntities = function ()
  {
    var i;
    for (i in TANK._objects)
    {
      if (TANK._objects.hasOwnProperty(i))
      {
        TANK.removeEntity(parseInt(i, 10));
      }
    }
  };

  // ### Register an object prefab
  // Use this to define an entity with a set of components that
  // can be instantiated later, like a blueprint.
  //
  // - `name`: The name of the prefab to store it under.
  // - `data`: A JSON object describing the components the prefab should contain,
  // with the following format:
  //
  //       {
  //         "Pos2D": { x: 0, y: 42 },
  //         "Velocity": {},
  //         "Collider": { width: 5, height: 5 },
  //       }
  TANK.addPrefab = function (name, data)
  {
    TANK._prefabs[name] = data;
  };

  // ### Get a prefab object
  //
  // - `name`: The name of the prefab to find.
  // - `return`: A prefab JSON object.
  TANK.getPrefab = function (name)
  {
    return TANK._prefabs[name];
  };

  // ### Register a new component type
  // Creates a new `Component` instance which defines a blueprint
  // for a type of component.
  //
  // - `componentName`: The name of the component type to register. This must be a valid identifier.
  // - `return`: A new instance of type `Component`.
  TANK.registerComponent = function (componentName)
  {
    // Warn about components with invalid identifiers
    if (componentName[0] >= 0 && componentName[0] <= 9 || componentName.search(" ") >= 0)
    {
      TANK.error(componentName + " is an invalid identifier and won't be accessible without [] operator");
      return;
    }

    var c = new TANK.Component(componentName);
    TANK._registeredComponents[componentName] = c;
    return c;
  };

  // ### Add a component to the engine
  // Components added to the engine are "global" and not
  // affected by spaces. These are commonly used to add systems
  // that contain global state, such as a graphics context.
  //
  // - `componentName`: The name of the component to add to the engine.
  TANK.addComponent = function (componentName)
  {
    // Check if we have this component already
    if (TANK[componentName])
    {
      return;
    }

    // Create engine entity if it doesn't exist
    if (!TANK._engineEntity)
    {
      TANK._engineEntity = TANK.createEntity();
    }

    TANK._engineEntity.addComponent(componentName);
    TANK[componentName] = TANK._engineEntity[componentName];

    // Only run initialize if the engine is already running
    // because all components are initialized on `TANK.start()`
    if (TANK._running)
    {
      TANK[componentName].initialize();
    }

    return TANK;
  };

  // ### Add multiple components to the engine
  // Components can be given as a string of comma seperated values,
  // a list of strings, or some combination of the above.
  // e.g., `TANK.addComponents("Pos2D, Velocity", "Image", "Collider");`
  TANK.addComponents = function ()
  {
    var i, j, arg;
    for (i = 0; i < arguments.length; ++i)
    {
      arg = arguments[i];
      arg = arg.replace(/\s/g, "");
      var components = arg.split(",");

      for (j = 0; j < components.length; ++j)
      {
        TANK.addComponent(components[j]);
      }
    }

    return this;
  };

  // ### Remove a component from the engine
  //
  // - `componentName`: The name of the component to remove.
  TANK.removeComponent = function (componentName)
  {
    var c = TANK[componentName];
    if (!c)
    {
      return;
    }

    if (TANK._engineEntity)
    {
      TANK._engineEntity.removeComponent(componentName);
      delete TANK[componentName];
    }
  };

  // ### Find components with a given interface
  // Gets all component instances that implement a particular interface.
  //
  // - `interfaceName`: Name of the interface that returned components should implement.
  // - `return`: An array of component instances.
  TANK.getComponentsWithInterface = function (interfaceName)
  {
    return TANK._interfaceComponents[interfaceName];
  };

  // ### Send out an event
  // Takes any number of arguments after event name.
  //
  // `eventName` - Name of the event to trigger.
  TANK.dispatchEvent = function (eventName)
  {
    // Get array of listeners
    var listeners = TANK._events[eventName];
    if (!listeners)
      return;

    // Construct arguments
    var message_args = [];
    for (var i = 1; i < arguments.length; ++i)
      message_args.push(arguments[i]);

    // Invoke the message on each listener
    var func, thisObj;
    for (var i in listeners)
    {
      func = listeners[i].func;
      thisObj = listeners[i].self;
      if (func)
        func.apply(thisObj, message_args);
      else
        TANK.log(thisObj + " is listening for " + eventName + " but does not implement a method of the same name");
    }
  };

  // ### Start the engine main loop
  TANK.start = function ()
  {
    TANK._lastTime = new Date();
    TANK._running = true;

    // Initialize all engine components
    TANK._engineEntity.invoke("initialize");

    update()
  };

  // ### Stop the engine
  TANK.stop = function ()
  {
    TANK._running = false
  };

  // ### Reset the engine
  // This deletes all game objects and resets the state of the engine.
  // Things like prefabs and component definitions are preserved.
  TANK.reset = function ()
  {
    TANK._resetting = true;
    TANK.removeAllEntities();
  };

  // ### Log a message to console
  //
  // `text` - Text to display.
  TANK.log = function (text)
  {
    if (!TANK.logsEnabled)
      return;

    console.log(text);
  };

  // ### Log a warning message to console
  //
  // `text` - Text to display.
  TANK.warn = function (text)
  {
    if (!TANK.warningsEnabled)
      return;

    console.warn(text);
  };

  // ### Log an error message to console
  //
  // `text` - Text to display.
  TANK.error = function (text)
  {
    if (!TANK.errorsEnabled)
      return;

    console.error(text);
  };

  function update()
  {
    // Get dt
    var new_time = new Date();
    var dt = (new_time - TANK._lastTime) / 1000.0;
    if (dt > 0.05)
      dt = 0.05;

    // Delete pending objects
    for (var i in TANK._objectsDeleted)
    {
      var obj = TANK._objectsDeleted[i];
      obj.destruct();
      delete TANK._objects[obj.id];
      delete TANK._objectsNamed[obj.name];
      obj.id = -1;
      obj.name = "Deleted";
    }
    TANK._objectsDeleted = [];

    // If we are resetting the engine, stop updating before the next frame but after
    // we've cleared up the deleted objects
    if (TANK._resetting)
    {
      // Remove all engine components
      for (var i in TANK._engineEntity._components)
        delete TANK[i];
      TANK._engineEntity.destruct();

      TANK._resetting = false;
      TANK._running = false;
      main();
      return;
    }

    // Dispatch enter frame message
    TANK.dispatchEvent("OnEnterFrame", dt);

    // Queue next frame
    if (TANK._running)
      requestAnimFrame(update);
  };

  // Map of objects tracked by the core
  // Key is the id of the object
  TANK._objects = {};

  // Secondary map of objects with name as key
  TANK._objectsNamed = {};

  // List of objects to delete
  TANK._objectsDeleted = [];

  // Map of prefabs with name as key
  TANK._prefabs = {};

  // "Proxy" entity that stores engine components
  TANK._engineEntity = null;

  // Map of current registered component types
  // Key is the name of the component
  TANK._registeredComponents = {};

  // Map of existing component instances sorted by tag names
  // Key is the name of the tag
  TANK._interfaceComponents = {};

  // Map of objects listening for a message
  TANK._events = {};

  // Current ID for game objects
  TANK._currentID = 0;

  // Last update time
  TANK._lastTime = new Date();

  // Whether or not the engine is running
  TANK._running = false;

  // Whether or not the engine is in the resetting state
  TANK._resetting = false;

}(this.TANK = this.TANK ||
{}));<|endoftext|>


<commit_before>var Class = require(__basedir + "/utilities/class");
var DeltaMergeable = require("./deltaMergeable");
var errors = require("../errors");
var serializer = require("../serializer");
var constants = require("../constants");
var moment = require('moment');

/**
 * @abstract
 * @class BaseGame - the base game plugin new games should inherit from.
 */
var BaseGame = Class(DeltaMergeable, {
    init: function(data) {
        DeltaMergeable.init.call(this);

        // serializable member variables
        this.players = [];
        this.gameObjects = {};
        this.session = (data.session === undefined ? "Unknown" : data.session);

        this._orders = [];
        this._newOrdersToPopIndex = 0;
        this._returnedDataTypeConverter = {};
        this._started = false;
        this._over = false;
        this._nextGameObjectID = 0;
        this._lastSerializableState = null;
        this._currentSerializableState = null;
        this._serializableDeltaState = null;
        this._deltas = []; // record of all delta states, for the game log generation

        this._initGameManager();

        this._addSerializableKeys(["players", "gameObjects", "session", "name"]);
    },

    // The following variable are static, and no game instances should override these, but their class prototypes can
    name: "Base Game", // should be overwritten by the child game class inheriting this
    numberOfPlayers: 2,
    maxInvalidsPerPlayer: 10,
    _orderFlag: {isOrderFlag: true},

    _initGameManager: function() {
        this._gameManager = require(__basedir + "/games/" + this.name.lowercaseFirst() + "/gameManager");
    },

    /////////////////////////////
    // Server starting methods //
    /////////////////////////////

    /**
     * Called then the game starts. Do not inherit this method, instead use begin()
     *
     * @param {Array.<Client>} clients - list of clients that are playing this game, and need a player
     */
    start: function(clients) {
        this._initPlayers(clients);

        var startData = this.begin();

        this._updateSerializableStates("start", startData);
        this._started = true;
    },

    /**
     * Returns a boolean representing if the game has started
     *
     * @returns {boolean} represents if the game has started yet.
     */
    hasStarted: function() {
        return this._started;
    },

    /**
     * Called when the game actually starts and has it's players. Intended to be inherited and extended when the game should be started (e.g. initializing game objects)
     *
     * @inheritable
     * @returns {*} returns any start data you want stored in the game log, such as the random seed (if any procedural generation is used)
     */
    begin: function() {
        // This should be inheritied in <gamename>/game.js. This function is simply here in case they delete the function because they don't need it (no idea why that would be the case though).
    },

    /////////////
    // Players //
    /////////////

    /**
     * Initializes the players based on what clients are connected.
     *
     * @param {Array.<Client>} clients - all client connected to this game that are playing, each will have a corresponding player created for it.
     */
    _initPlayers: function(clients) {
        for(var i = 0; i < clients.length; i++) {
            var client = clients[i];
            var player = this.create("Player", { // this method should be implimented in GeneratedGame
                name: client.name || ("Player " + i),
                clientType: client.type || "Unknown",
            });

            player.errors = [];
            player.timeRemaining = player.timeRemaining || 1e10; // 10 seconds in nanoseconds
            player.client = client;
            client.setGameData(this, player);
            this.players.push(player);
        }
    },

    /**
     * Called when a client disconnected to remove the client from the game and check if they have a player and if removing them alters the game
     *
     * @param {Player} player - the player whose client disconnected
     * @param {string} reason - human readable resason why this player disconnected
     */
    playerDisconnected: function(player, reason) {
        if(player && this.hasStarted() && !this.isOver()) {
            this.declairLoser(player, reason || "Disconnected during gameplay.");
        }
    },

    //////////////////
    // Game Objects //
    //////////////////

    /**
     * Checks and returns the game object with given id, undefined otherwise.
     *
     * @returns {BaseGameObject} with the given id
     */
    getGameObject: function(id) { // TO INHERIT
        id = parseInt(id);
        if(id !== NaN) {
            return this.gameObjects[id];
        }
    },

    /**
     * Checks if the given object represents a game object in this game instance.
     *
     * @returns {boolean} representing if the passed in obj is a tracked game object in this game
     */
    isGameObject: function(obj) {
        return (serializer.isObject(obj) && obj.id !== undefined && this.getGameObject(obj.id) === obj);
    },

    /**
     * returns the next availible game object id, which by default is the number number as a string. But games where linear id numbers give away info this can be overridden to hide.
     *
     * @returns {string} the next id for a game object. It should never be an id already used this instance, even if that game object is dereferenced everwhere.
     */
    _generateNextGameObjectID: function() {
        return String(this._nextGameObjectID++); // returns this._nextGameObjectID then increments by 1 (that's how post++ works FYI)
    },

    /**
     * Creates and tracks a new game object.
     *
     * @param {string} gameObjectName - the name of the game object class
     * @param {Object} [data] - initialization data for new game object
     * @returns {BaseGameObject} the game object that was created, now being tracked by this game
     */
    create: function(gameObjectName, data) {
        data = data || {};
        data.id = this._generateNextGameObjectID();
        data.game = this;
        var gameObject = this._gameManager.createGameObject(gameObjectName, data);
        this.gameObjects[gameObject.id] = gameObject;
        return gameObject;
    },

    /////////////////////////////////
    // Client Responses & Requests //
    /////////////////////////////////

    /**
     * Called when a session gets the "finished" event from an ai (client), meaning they finished an order we instructed them to do.
     *
     * @throws {CerveauError} - game logic or event errors
     * @param {Player} the player this ai controls
     * @param {number} orderIndex - the index of the order that finished
     * @param {Object} [data] - serialized data returned from the ai executing that order
     * @returns {Object|undefined} if an error was encountered an invalid object will be returned, undefined if everything went correctly.
     */
    aiFinished: function(player, orderIndex, data) {
        var order = this._orders[orderIndex];
        if(order === undefined) {
            this.throwErrorClass(errors.EventDataError, "no order found that you claim to have finished.");
        }
        else {
            var finished = order.name;
            var defaultCallback = this["aiFinished_" + finished];

            var returned = serializer.deserialize(data, this);
            returned = this._gameManager.sanitizeFinished(order, returned);

            var hadCallback = true;
            if(order.callback) {
                order.callback(returned);
            }
            else if(defaultCallback) {
                defaultCallback.call(this, player, returned);
            }
            else {
                hadCallback = false;
            }

            if(hadCallback) {
                this._updateSerializableStates("finished", {
                    player: serializer.serialize(player, this),
                    order: finished,
                    returned: data,
                });
            }
            else {
                this.throwErrorClass(errors.EventDataError, "No callback for finshed order '" + finished + "'.");
            }
        }
    },

    /**
     * Called when a session gets the "run" event from an ai (client), meaning they want the game to run some game logic.
     *
     * @param {Player} player - the player this ai controls
     * @param {Object} data - serialized data containing what game logic to run.
     * @returns {Promise} A promise that should eventually resolve to whatever the game logic returned from running the 'run' command.
     */
    aiRun: function(player, data) {
        var run = serializer.deserialize(data, this);
        var runCallback = run.caller[run.functionName];
        var ran = {};

        var argsArray = this._gameManager.sanitizeRun(run.caller.gameObjectName, run.functionName, run.args || {});
        var asyncReturnWrapper = {}; // just an object both asyncReturn and the promise have scope to to pass things to and from.
        var asyncReturn = function(asyncReturnValue) { asyncReturnWrapper.callback(asyncReturnValue); }; // callback function setup below

        argsArray.unshift(player);
        argsArray.push(asyncReturn);

        var self = this;
        var sanitizeRan = function(returned) {
            return self._gameManager.sanitizeRan(run.caller.gameObjectName, run.functionName, returned);
        }

        return new Promise(function(resolve, reject) {
            try {
                var ranReturned = runCallback.apply(run.caller, argsArray);

                self._updateSerializableStates("run", {
                    player: serializer.serialize(player, self),
                    data: data,
                });

                if(ranReturned === BaseGame._orderFlag) { // then they want to execute an order, and are thus returning the value asyncronously
                    asyncReturnWrapper.callback = function(asyncReturnValue) {
                        resolve(sanitizeRan(asyncReturnValue));
                    };
                }
                else {
                    resolve(sanitizeRan(ranReturned));
                }
            }
            catch(e) {
                reject(e);
            }
        });
    },

    /**
     * Called internally by games to order ais (clients) to execute some order.
     *
     * @param {Player} player - the player that we want to execture the order
     * @param {string} orderName - the name of the order to the player's ai to execute
     * @param {Array} [args] - an array that represents the args to send to the order function on the client ai, or [callback] if no args
     * @param {function} [callback] - callback function to execute instead of the normal aiFinished callback
     */
    order: function(player, orderName, args, callback) {
        if(callback === undefined && typeof(args) == "function") {
            callback = args;
        }

        var order = {
            player: player,
            index: this._orders.length,
            name: orderName,
            args: args || [],
            callback: callback,
        };
        this._gameManager.sanitizeOrder(order);

        this._orders.push(order);

        return BaseGame._orderFlag;
    },

    /**
     * Called from the session to send this order to the ais (clients). Pops all orders added since the last time this was called
     *
     * @returns {Array.<Object>} array of orders to send
     */
    popOrders: function() {
        var orders = [];
        for(var i = this._newOrdersToPopIndex; i < this._orders.length; i++) {
            orders.push(this._orders[i]);
        }
        this._newOrdersToPopIndex = this._orders.length;
        return orders;
    },

    ///////////////////////////
    // States & Delta States //
    ///////////////////////////

    /**
     * Returns the difference between the last and current state for the given player.
     *
     * @param {Player} player (for inheritance) if the state differs between players inherited games can send different states (such as to hide data from certain players).
     * @returns {Object} the serializable state as the passed in player should see it
     */
    getSerializableDeltaStateFor: function(player) {
        return this._serializableDeltaState;
    },

    /**
     * Updates all the private states used to generate delta states and game logs.
     *
     * @param {string} deltaType - the type of delta that occured (the reason why the state changed)
     * @param {Object} [deltaData] - data about the delta, such as parameters sent that changed it
     */
    _updateSerializableStates: function(deltaType, deltaData) {
        this.hasStateChanged = false;

        var last = this._currentSerializableState || {};
        var current = serializer.serialize(this);
        var delta = serializer.getDelta(last, current);

        if(delta && !serializer.isEmpty(delta)) { // then there is an actual difference between the last and current state
            this.hasStateChanged = true;
            this._lastSerializableState = last;
            this._currentSerializableState = current;
            this._serializableDeltaState = delta;
        }

        this._deltas.push({
            type: deltaType,
            data: deltaData,
            game: delta,
        });
    },

    /**
     * Generates the game log from all the events that happened in this game.
     *
     * @returns {Object} the gamelog to store somewhere and somehow (GameLogger handles that)
     */
    generateGamelog: function() {
        var winners = [];
        var losers = [];

        for(var i = 0; i < this.players.length; i++) {
            var player = this.players[i];
            if(player.won) {
                winners.push(player.id);
            }
            else {
                losers.push(player.id);
            }
        }

        return {
            gameName: this.name,
            gameSession: this.session,
            deltas: this._deltas,
            constants: constants.shared,
            epoch: moment().valueOf(),
            winners: winners,
            losers: losers,
        };
    },

    /////////////////////////
    // Winning and Loosing //
    /////////////////////////

    /**
     * Checks if a game is over
     *
     * @returns {boolean} representing if this game is over
     */
    isOver: function(isOver) {
        if(isOver) {
            this._over = true;
        }

        return this._over;
    },

    /**
     * Throws an error of specified type while checking to see if that player lost
     *
     * @param {CerveaError} errorClass - the CerveauError class or subclass this error is of type
     * @param {Player} player - player that did something invalid
     * @param {string} reason - the human readable message why the logic is invalid
     * @param {Object} [data] - any additional info about why the game logic is invalid
     */
    throwErrorClass: function(errorClass, player, message, data) {
        var error = new errorClass(message, data);
        player.errors.push(error);

        if(player.error.length > this.maxErrorsPerPlayer) {
            this.declairLoser(player, "Reached max amount of errors in one game (" + this.maxErrorsPerPlayer + ")");
        }

        throw error;
    },

    /**
     * Throws invalid game logic error
     *
     * @see throwErrorOf
     */
    throwInvalidGameLogic: function(player, message, data) {
        return this.throwErrorClass(error.GameLogicError, player, message, data);
    },

    /**
     * Declairs a player as having lost, and assumes when a player looses the rest could still be competing to win.
     *
     * @param {Player} the player that lost the game
     * @param {string} (optional) human readable string that is the lose reason
     * @param {Object} (optional) flags: 'dontCheckForWinner' key to set to not check for winner
     */
    declairLoser: function(loser, reason, flags) {
        loser.lost = true;
        loser.reasonLost = reason || "Lost";
        loser.won = false;
        loser.reasonWon = "";

        if(!flags || !flags.dontCheckForWinner) {
            this.checkForWinner();
        }
    },

    /**
     * Declairs the player as winning, assumes when a player wins the rest lose (unless they've already been set to win)
     *
     * @param {Player} the player that won the game, the rest loose if not already won
     * @param {string} (optional) the human readable string why they won the game
     */
    declairWinner: function(winner, reason) {
        winner.won = true;
        winner.reasonWon = reason || "Won";
        winner.lost = false;
        winner.reasonLost = "";

        for(var i = 0; i < this.players.length; i++) {
            var player = this.players[i];

            if(player !== winner && !player.won && !player.lost) { // then this player has not lost yet and now looses because someone else won
                this.declairLoser(player, "Other player won", {dontCheckForWinner: true});
            }
        }

        this.isOver(true);
    },

    /**
     * Checks if this game is over because there is a winner (all other players have lost).
     *
     * @returns {boolean} boolean represnting if the game is over
     */
    checkForWinner: function() {
        var winner;
        for(var i = 0; i < this.players.length; i++) {
            var player = this.players[i];

            if(!player.lost) {
                if(winner) {
                    return false;
                }
                else {
                    winner = player;
                }
            }
        }

        if(winner) {
            this.declairWinner(winner, "All other players lost.");
            return true;
        }
    },
});

module.exports = BaseGame
<commit_msg>More winner/loser information in gamelogs as per request<commit_after>var Class = require(__basedir + "/utilities/class");
var DeltaMergeable = require("./deltaMergeable");
var errors = require("../errors");
var serializer = require("../serializer");
var constants = require("../constants");
var moment = require('moment');

/**
 * @abstract
 * @class BaseGame - the base game plugin new games should inherit from.
 */
var BaseGame = Class(DeltaMergeable, {
    init: function(data) {
        DeltaMergeable.init.call(this);

        // serializable member variables
        this.players = [];
        this.gameObjects = {};
        this.session = (data.session === undefined ? "Unknown" : data.session);

        this._orders = [];
        this._newOrdersToPopIndex = 0;
        this._returnedDataTypeConverter = {};
        this._started = false;
        this._over = false;
        this._nextGameObjectID = 0;
        this._lastSerializableState = null;
        this._currentSerializableState = null;
        this._serializableDeltaState = null;
        this._deltas = []; // record of all delta states, for the game log generation

        this._initGameManager();

        this._addSerializableKeys(["players", "gameObjects", "session", "name"]);
    },

    // The following variable are static, and no game instances should override these, but their class prototypes can
    name: "Base Game", // should be overwritten by the child game class inheriting this
    numberOfPlayers: 2,
    maxInvalidsPerPlayer: 10,
    _orderFlag: {isOrderFlag: true},

    _initGameManager: function() {
        this._gameManager = require(__basedir + "/games/" + this.name.lowercaseFirst() + "/gameManager");
    },

    /////////////////////////////
    // Server starting methods //
    /////////////////////////////

    /**
     * Called then the game starts. Do not inherit this method, instead use begin()
     *
     * @param {Array.<Client>} clients - list of clients that are playing this game, and need a player
     */
    start: function(clients) {
        this._initPlayers(clients);

        var startData = this.begin();

        this._updateSerializableStates("start", startData);
        this._started = true;
    },

    /**
     * Returns a boolean representing if the game has started
     *
     * @returns {boolean} represents if the game has started yet.
     */
    hasStarted: function() {
        return this._started;
    },

    /**
     * Called when the game actually starts and has it's players. Intended to be inherited and extended when the game should be started (e.g. initializing game objects)
     *
     * @inheritable
     * @returns {*} returns any start data you want stored in the game log, such as the random seed (if any procedural generation is used)
     */
    begin: function() {
        // This should be inheritied in <gamename>/game.js. This function is simply here in case they delete the function because they don't need it (no idea why that would be the case though).
    },

    /////////////
    // Players //
    /////////////

    /**
     * Initializes the players based on what clients are connected.
     *
     * @param {Array.<Client>} clients - all client connected to this game that are playing, each will have a corresponding player created for it.
     */
    _initPlayers: function(clients) {
        for(var i = 0; i < clients.length; i++) {
            var client = clients[i];
            var player = this.create("Player", { // this method should be implimented in GeneratedGame
                name: client.name || ("Player " + i),
                clientType: client.type || "Unknown",
            });

            player.errors = [];
            player.timeRemaining = player.timeRemaining || 1e10; // 10 seconds in nanoseconds
            player.client = client;
            client.setGameData(this, player);
            this.players.push(player);
        }
    },

    /**
     * Called when a client disconnected to remove the client from the game and check if they have a player and if removing them alters the game
     *
     * @param {Player} player - the player whose client disconnected
     * @param {string} reason - human readable resason why this player disconnected
     */
    playerDisconnected: function(player, reason) {
        if(player && this.hasStarted() && !this.isOver()) {
            this.declairLoser(player, reason || "Disconnected during gameplay.");
        }
    },

    //////////////////
    // Game Objects //
    //////////////////

    /**
     * Checks and returns the game object with given id, undefined otherwise.
     *
     * @returns {BaseGameObject} with the given id
     */
    getGameObject: function(id) { // TO INHERIT
        id = parseInt(id);
        if(id !== NaN) {
            return this.gameObjects[id];
        }
    },

    /**
     * Checks if the given object represents a game object in this game instance.
     *
     * @returns {boolean} representing if the passed in obj is a tracked game object in this game
     */
    isGameObject: function(obj) {
        return (serializer.isObject(obj) && obj.id !== undefined && this.getGameObject(obj.id) === obj);
    },

    /**
     * returns the next availible game object id, which by default is the number number as a string. But games where linear id numbers give away info this can be overridden to hide.
     *
     * @returns {string} the next id for a game object. It should never be an id already used this instance, even if that game object is dereferenced everwhere.
     */
    _generateNextGameObjectID: function() {
        return String(this._nextGameObjectID++); // returns this._nextGameObjectID then increments by 1 (that's how post++ works FYI)
    },

    /**
     * Creates and tracks a new game object.
     *
     * @param {string} gameObjectName - the name of the game object class
     * @param {Object} [data] - initialization data for new game object
     * @returns {BaseGameObject} the game object that was created, now being tracked by this game
     */
    create: function(gameObjectName, data) {
        data = data || {};
        data.id = this._generateNextGameObjectID();
        data.game = this;
        var gameObject = this._gameManager.createGameObject(gameObjectName, data);
        this.gameObjects[gameObject.id] = gameObject;
        return gameObject;
    },

    /////////////////////////////////
    // Client Responses & Requests //
    /////////////////////////////////

    /**
     * Called when a session gets the "finished" event from an ai (client), meaning they finished an order we instructed them to do.
     *
     * @throws {CerveauError} - game logic or event errors
     * @param {Player} the player this ai controls
     * @param {number} orderIndex - the index of the order that finished
     * @param {Object} [data] - serialized data returned from the ai executing that order
     * @returns {Object|undefined} if an error was encountered an invalid object will be returned, undefined if everything went correctly.
     */
    aiFinished: function(player, orderIndex, data) {
        var order = this._orders[orderIndex];
        if(order === undefined) {
            this.throwErrorClass(errors.EventDataError, "no order found that you claim to have finished.");
        }
        else {
            var finished = order.name;
            var defaultCallback = this["aiFinished_" + finished];

            var returned = serializer.deserialize(data, this);
            returned = this._gameManager.sanitizeFinished(order, returned);

            var hadCallback = true;
            if(order.callback) {
                order.callback(returned);
            }
            else if(defaultCallback) {
                defaultCallback.call(this, player, returned);
            }
            else {
                hadCallback = false;
            }

            if(hadCallback) {
                this._updateSerializableStates("finished", {
                    player: serializer.serialize(player, this),
                    order: finished,
                    returned: data,
                });
            }
            else {
                this.throwErrorClass(errors.EventDataError, "No callback for finshed order '" + finished + "'.");
            }
        }
    },

    /**
     * Called when a session gets the "run" event from an ai (client), meaning they want the game to run some game logic.
     *
     * @param {Player} player - the player this ai controls
     * @param {Object} data - serialized data containing what game logic to run.
     * @returns {Promise} A promise that should eventually resolve to whatever the game logic returned from running the 'run' command.
     */
    aiRun: function(player, data) {
        var run = serializer.deserialize(data, this);
        var runCallback = run.caller[run.functionName];
        var ran = {};

        var argsArray = this._gameManager.sanitizeRun(run.caller.gameObjectName, run.functionName, run.args || {});
        var asyncReturnWrapper = {}; // just an object both asyncReturn and the promise have scope to to pass things to and from.
        var asyncReturn = function(asyncReturnValue) { asyncReturnWrapper.callback(asyncReturnValue); }; // callback function setup below

        argsArray.unshift(player);
        argsArray.push(asyncReturn);

        var self = this;
        var sanitizeRan = function(returned) {
            return self._gameManager.sanitizeRan(run.caller.gameObjectName, run.functionName, returned);
        }

        return new Promise(function(resolve, reject) {
            try {
                var ranReturned = runCallback.apply(run.caller, argsArray);

                self._updateSerializableStates("run", {
                    player: serializer.serialize(player, self),
                    data: data,
                });

                if(ranReturned === BaseGame._orderFlag) { // then they want to execute an order, and are thus returning the value asyncronously
                    asyncReturnWrapper.callback = function(asyncReturnValue) {
                        resolve(sanitizeRan(asyncReturnValue));
                    };
                }
                else {
                    resolve(sanitizeRan(ranReturned));
                }
            }
            catch(e) {
                reject(e);
            }
        });
    },

    /**
     * Called internally by games to order ais (clients) to execute some order.
     *
     * @param {Player} player - the player that we want to execture the order
     * @param {string} orderName - the name of the order to the player's ai to execute
     * @param {Array} [args] - an array that represents the args to send to the order function on the client ai, or [callback] if no args
     * @param {function} [callback] - callback function to execute instead of the normal aiFinished callback
     */
    order: function(player, orderName, args, callback) {
        if(callback === undefined && typeof(args) == "function") {
            callback = args;
        }

        var order = {
            player: player,
            index: this._orders.length,
            name: orderName,
            args: args || [],
            callback: callback,
        };
        this._gameManager.sanitizeOrder(order);

        this._orders.push(order);

        return BaseGame._orderFlag;
    },

    /**
     * Called from the session to send this order to the ais (clients). Pops all orders added since the last time this was called
     *
     * @returns {Array.<Object>} array of orders to send
     */
    popOrders: function() {
        var orders = [];
        for(var i = this._newOrdersToPopIndex; i < this._orders.length; i++) {
            orders.push(this._orders[i]);
        }
        this._newOrdersToPopIndex = this._orders.length;
        return orders;
    },

    ///////////////////////////
    // States & Delta States //
    ///////////////////////////

    /**
     * Returns the difference between the last and current state for the given player.
     *
     * @param {Player} player (for inheritance) if the state differs between players inherited games can send different states (such as to hide data from certain players).
     * @returns {Object} the serializable state as the passed in player should see it
     */
    getSerializableDeltaStateFor: function(player) {
        return this._serializableDeltaState;
    },

    /**
     * Updates all the private states used to generate delta states and game logs.
     *
     * @param {string} deltaType - the type of delta that occured (the reason why the state changed)
     * @param {Object} [deltaData] - data about the delta, such as parameters sent that changed it
     */
    _updateSerializableStates: function(deltaType, deltaData) {
        this.hasStateChanged = false;

        var last = this._currentSerializableState || {};
        var current = serializer.serialize(this);
        var delta = serializer.getDelta(last, current);

        if(delta && !serializer.isEmpty(delta)) { // then there is an actual difference between the last and current state
            this.hasStateChanged = true;
            this._lastSerializableState = last;
            this._currentSerializableState = current;
            this._serializableDeltaState = delta;
        }

        this._deltas.push({
            type: deltaType,
            data: deltaData,
            game: delta,
        });
    },

    /**
     * Generates the game log from all the events that happened in this game.
     *
     * @returns {Object} the gamelog to store somewhere and somehow (GameLogger handles that)
     */
    generateGamelog: function() {
        var winners = [];
        var losers = [];

        for(var i = 0; i < this.players.length; i++) {
            var player = this.players[i];

            (player.won ? winners : losers).push({
                index: i,
                id: player.id,
                name: player.name,
            });
        }

        return {
            gameName: this.name,
            gameSession: this.session,
            deltas: this._deltas,
            constants: constants.shared,
            epoch: moment().valueOf(),
            winners: winners,
            losers: losers,
        };
    },

    /////////////////////////
    // Winning and Loosing //
    /////////////////////////

    /**
     * Checks if a game is over, or sets if a game is over
     *
     * @param {boolean} [isOver] - if you pass in true this sets the game to over
     * @returns {boolean} true if this game is over, false otherwise
     */
    isOver: function(isOver) {
        if(isOver === true) {
            this._over = true;
        }

        return this._over;
    },

    /**
     * Throws an error of specified type while checking to see if that player lost
     *
     * @param {CerveaError} errorClass - the CerveauError class or subclass this error is of type
     * @param {Player} player - player that did something invalid
     * @param {string} reason - the human readable message why the logic is invalid
     * @param {Object} [data] - any additional info about why the game logic is invalid
     */
    throwErrorClass: function(errorClass, player, message, data) {
        var error = new errorClass(message, data);
        player.errors.push(error);

        if(player.error.length > this.maxErrorsPerPlayer) {
            this.declairLoser(player, "Reached max amount of errors in one game (" + this.maxErrorsPerPlayer + ")");
        }

        throw error;
    },

    /**
     * Throws invalid game logic error
     *
     * @see throwErrorOf
     */
    throwInvalidGameLogic: function(player, message, data) {
        return this.throwErrorClass(error.GameLogicError, player, message, data);
    },

    /**
     * Declairs a player as having lost, and assumes when a player looses the rest could still be competing to win.
     *
     * @param {Player} the player that lost the game
     * @param {string} (optional) human readable string that is the lose reason
     * @param {Object} (optional) flags: 'dontCheckForWinner' key to set to not check for winner
     */
    declairLoser: function(loser, reason, flags) {
        loser.lost = true;
        loser.reasonLost = reason || "Lost";
        loser.won = false;
        loser.reasonWon = "";

        if(!flags || !flags.dontCheckForWinner) {
            this.checkForWinner();
        }
    },

    /**
     * Declairs the player as winning, assumes when a player wins the rest lose (unless they've already been set to win)
     *
     * @param {Player} the player that won the game, the rest loose if not already won
     * @param {string} (optional) the human readable string why they won the game
     */
    declairWinner: function(winner, reason) {
        winner.won = true;
        winner.reasonWon = reason || "Won";
        winner.lost = false;
        winner.reasonLost = "";

        for(var i = 0; i < this.players.length; i++) {
            var player = this.players[i];

            if(player !== winner && !player.won && !player.lost) { // then this player has not lost yet and now looses because someone else won
                this.declairLoser(player, "Other player won", {dontCheckForWinner: true});
            }
        }

        this.isOver(true);
    },

    /**
     * Checks if this game is over because there is a winner (all other players have lost).
     *
     * @returns {boolean} boolean represnting if the game is over
     */
    checkForWinner: function() {
        var winner;
        for(var i = 0; i < this.players.length; i++) {
            var player = this.players[i];

            if(!player.lost) {
                if(winner) {
                    return false;
                }
                else {
                    winner = player;
                }
            }
        }

        if(winner) {
            this.declairWinner(winner, "All other players lost.");
            return true;
        }
    },
});

module.exports = BaseGame
<|endoftext|>


<commit_before>import React from 'react'
import { Link } from 'react-router'

// ES6 class syntax
class Header extends React.Component {
  render () {
    // utilSpace is either the search input box, or a link to the search page. will be rendered where searchbar goes.
    let utilSpace
    if (this.props.showSearch) {
      utilSpace = <input onChange={this.props.handleSearchTermChange} value={this.props.searchTerm} type='text' placeholder='search' />
    } else {
      utilSpace = (

          <Link to='/search'>
            back
          </Link>

      )
    }
    return (

          <Link to='/'>
            Bizzy Bear's Video Emporium
          </Link>

        {utilSpace}

    )
  }
}

const { func, bool, string } = React.PropTypes
Header.propTypes = {
  handleSearchTermChange: func,
  showSearch: bool,
  searchTerm: string
}

export default Header
<commit_msg>Add redux to search input in Header.<commit_after>import React from 'react'
import { connect } from 'react-redux'
import { setSearchTerm } from './actionCreators'
import { Link } from 'react-router'

// ES6 class syntax
class Header extends React.Component {
  constructor (props) {
    // ensure that this refers to Header when we need it to via constructor - super(props) sets props to inherit from Header
    super(props)
    // .bind(this) on handleSearchTermChange
    this.handleSearchTermChange = this.handleSearchTermChange.bind(this)
  }
  handleSearchTermChange (event) {
    this.props.dispatch(setSearchTerm(event.target.value))
  }
  // no comma needed after handleSearchTermChange because this is ES6 class syntax
  render () {
    // utilSpace is either the search input box, or a link to the search page. will be rendered where searchbar goes.
    let utilSpace
    if (this.props.showSearch) {
      utilSpace = <input onChange={this.handleSearchTermChange} value={this.props.searchTerm} type='text' placeholder='search' />
    } else {
      utilSpace = (

          <Link to='/search'>
            back
          </Link>

      )
    }
    return (

          <Link to='/'>
            Bizzy Bear's Video Emporium
          </Link>

        {utilSpace}

    )
  }
}

const { func, bool, string } = React.PropTypes
Header.propTypes = {
  dispatch: func,
  showSearch: bool,
  searchTerm: string
}

const mapStateToProps = (state) => {
  return {
    searchTerm: state.searchTerm
  }
}

export default connect(mapStateToProps)(Header)
<|endoftext|>