diff --git a/.gitignore b/.gitignore index 65a22c8..a6d0efa 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ /*.dump spec/examples.txt + +.rake_tasks~ diff --git a/.ruby-version b/.ruby-version index 7d2ed7c..530cdd9 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.1.4 +2.2.4 diff --git a/.travis.yml b/.travis.yml index a80b6e0..d37577c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ -language: node_js -node_js: - - '0.10' +language: ruby before_script: - - 'npm install -g bower grunt-cli' - - 'bower install' + - 'RAILS_ENV=test bundle exec rake db:create db:migrate' +script: + - 'RAILS_ENV=test bundle exec rake spec' + - 'RAILS_ENV=test bundle exec rake spec:javascript' diff --git a/Gemfile b/Gemfile index 4e4d0c1..062eb32 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source 'https://rubygems.org' -ruby '2.1.4' +ruby '2.2.4' gem 'rails', '~> 4.2.0' gem 'uglifier', '>= 1.3.0' @@ -8,6 +8,7 @@ gem 'sdoc', '~> 0.4.0', group: :doc gem 'spring', group: :development gem 'pg', '~> 0.17.1' gem 'responders', '~> 2.1.1' +gem 'react-rails', '~> 1.10.0' group :development, :test do gem 'rspec-rails', '~> 3.4.0' @@ -15,6 +16,7 @@ group :development, :test do gem 'factory_girl_rails', '~> 4.4.1' gem 'database_cleaner', '~> 1.5.1' gem 'faker', '~> 1.6.1' + gem 'jasmine-rails', '~> 0.14.1' end group :test do @@ -23,9 +25,10 @@ group :test do end gem 'spring-commands-rspec', group: :development -gem 'thin', '~> 1.6.2' -gem 'sass', '~> 3.4.5' -gem 'compass', '~> 1.0.1' +gem 'thin', '~> 1.7.0' +gem 'sass-rails', '~> 5.0.6' +gem 'compass-rails', '~> 3.0.2' +gem 'bootstrap-sass', '~> 3.3.7' gem 'will_paginate', '~> 3.0.7' gem 'geokit-rails', '~> 2.0.1' gem 'countries', '~> 0.9.3' diff --git a/Gemfile.lock b/Gemfile.lock index 353552d..43076c2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,147 +1,178 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (4.2.0) - actionpack (= 4.2.0) - actionview (= 4.2.0) - activejob (= 4.2.0) + actionmailer (4.2.7.1) + actionpack (= 4.2.7.1) + actionview (= 4.2.7.1) + activejob (= 4.2.7.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.0) - actionview (= 4.2.0) - activesupport (= 4.2.0) - rack (~> 1.6.0) + actionpack (4.2.7.1) + actionview (= 4.2.7.1) + activesupport (= 4.2.7.1) + rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.1) - actionview (4.2.0) - activesupport (= 4.2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (4.2.7.1) + activesupport (= 4.2.7.1) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.1) - activejob (4.2.0) - activesupport (= 4.2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + activejob (4.2.7.1) + activesupport (= 4.2.7.1) globalid (>= 0.3.0) - activemodel (4.2.0) - activesupport (= 4.2.0) + activemodel (4.2.7.1) + activesupport (= 4.2.7.1) builder (~> 3.1) - activerecord (4.2.0) - activemodel (= 4.2.0) - activesupport (= 4.2.0) + activerecord (4.2.7.1) + activemodel (= 4.2.7.1) + activesupport (= 4.2.7.1) arel (~> 6.0) - activesupport (4.2.0) + activesupport (4.2.7.1) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - addressable (2.3.6) - arel (6.0.0) - builder (3.2.2) - chunky_png (1.3.1) - compass (1.0.1) + addressable (2.5.0) + public_suffix (~> 2.0, >= 2.0.2) + arel (6.0.4) + autoprefixer-rails (6.7.0) + execjs + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.0) + bootstrap-sass (3.3.7) + autoprefixer-rails (>= 5.2.1) + sass (>= 3.3.4) + builder (3.2.3) + chunky_png (1.3.8) + coffee-script-source (1.12.2) + compass (1.0.3) chunky_png (~> 1.2) - compass-core (~> 1.0.1) + compass-core (~> 1.0.2) compass-import-once (~> 1.0.5) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) sass (>= 3.3.13, < 3.5) - compass-core (1.0.1) + compass-core (1.0.3) multi_json (~> 1.0) sass (>= 3.3.0, < 3.5) compass-import-once (1.0.5) sass (>= 3.2, < 3.5) + compass-rails (3.0.2) + compass (~> 1.0.0) + sass-rails (< 5.1) + sprockets (< 4.0) + concurrent-ruby (1.0.4) + connection_pool (2.2.1) countries (0.9.3) currencies (~> 0.4.2) - crack (0.4.2) + crack (0.4.3) safe_yaml (~> 1.0.0) currencies (0.4.2) - daemons (1.1.9) - database_cleaner (1.5.1) - diff-lcs (1.2.5) + daemons (1.2.4) + database_cleaner (1.5.3) + diff-lcs (1.3) erubis (2.7.0) - eventmachine (1.0.3) - execjs (2.2.1) + eventmachine (1.2.1) + execjs (2.7.0) factory_girl (4.4.0) activesupport (>= 3.0.0) factory_girl_rails (4.4.1) factory_girl (~> 4.4.0) railties (>= 3.0.0) - faker (1.6.1) + faker (1.6.6) i18n (~> 0.5) - ffi (1.9.5) + ffi (1.9.17) geoip (1.4.0) - geokit (1.9.0) - multi_json (>= 1.3.2) + geokit (1.10.0) geokit-rails (2.0.1) geokit (~> 1.5) rails (>= 3.0) - globalid (0.3.0) + globalid (0.3.7) activesupport (>= 4.1.0) - hike (1.2.3) i18n (0.7.0) - jbuilder (2.1.3) - activesupport (>= 3.0.0, < 5) + jasmine-core (2.5.2) + jasmine-rails (0.14.1) + jasmine-core (>= 1.3, < 3.0) + phantomjs (>= 1.9) + railties (>= 3.2.0) + sprockets-rails + jbuilder (2.6.1) + activesupport (>= 3.0.0, < 5.1) multi_json (~> 1.2) - json (1.8.2) - loofah (2.0.1) + json (1.8.6) + loofah (2.0.3) nokogiri (>= 1.5.9) - mail (2.6.3) - mime-types (>= 1.16, < 3) - mime-types (2.4.3) - mini_portile (0.6.2) - minitest (5.5.1) - multi_json (1.10.1) - nokogiri (1.6.5) - mini_portile (~> 0.6.0) + mail (2.6.4) + mime-types (>= 1.16, < 4) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_portile2 (2.1.0) + minitest (5.10.1) + multi_json (1.12.1) + nokogiri (1.7.0.1) + mini_portile2 (~> 2.1.0) pg (0.17.1) - rack (1.6.0) + phantomjs (2.1.1.0) + public_suffix (2.0.5) + rack (1.6.5) rack-test (0.6.3) rack (>= 1.0) - rails (4.2.0) - actionmailer (= 4.2.0) - actionpack (= 4.2.0) - actionview (= 4.2.0) - activejob (= 4.2.0) - activemodel (= 4.2.0) - activerecord (= 4.2.0) - activesupport (= 4.2.0) + rails (4.2.7.1) + actionmailer (= 4.2.7.1) + actionpack (= 4.2.7.1) + actionview (= 4.2.7.1) + activejob (= 4.2.7.1) + activemodel (= 4.2.7.1) + activerecord (= 4.2.7.1) + activesupport (= 4.2.7.1) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.0) + railties (= 4.2.7.1) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.5) + rails-dom-testing (1.0.8) activesupport (>= 4.2.0.beta, < 5.0) - nokogiri (~> 1.6.0) + nokogiri (~> 1.6) rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.1) + rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (4.2.0) - actionpack (= 4.2.0) - activesupport (= 4.2.0) + railties (4.2.7.1) + actionpack (= 4.2.7.1) + activesupport (= 4.2.7.1) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (10.4.2) - rb-fsevent (0.9.4) - rb-inotify (0.9.5) + rake (12.0.0) + rb-fsevent (0.9.8) + rb-inotify (0.9.8) ffi (>= 0.5.0) - rdoc (4.1.2) - json (~> 1.4) - responders (2.1.1) + rdoc (4.3.0) + react-rails (1.10.0) + babel-transpiler (>= 0.7.0) + coffee-script-source (~> 1.8) + connection_pool + execjs + railties (>= 3.2) + tilt + responders (2.1.2) railties (>= 4.2.0, < 5.1) - rspec-collection_matchers (1.1.2) + rspec-collection_matchers (1.1.3) rspec-expectations (>= 2.99.0.beta1) - rspec-core (3.4.1) + rspec-core (3.4.4) rspec-support (~> 3.4.0) rspec-expectations (3.4.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.4.0) - rspec-mocks (3.4.0) + rspec-mocks (3.4.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.4.0) - rspec-rails (3.4.0) + rspec-rails (3.4.2) actionpack (>= 3.0, < 4.3) activesupport (>= 3.0, < 4.3) railties (>= 3.0, < 4.3) @@ -151,66 +182,76 @@ GEM rspec-support (~> 3.4.0) rspec-support (3.4.1) safe_yaml (1.0.4) - sass (3.4.5) - sdoc (0.4.1) + sass (3.4.23) + sass-rails (5.0.6) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + sdoc (0.4.2) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) - spring (1.1.3) - spring-commands-rspec (1.0.2) + spring (2.0.1) + activesupport (>= 4.2) + spring-commands-rspec (1.0.4) spring (>= 0.9.1) - sprockets (2.12.3) - hike (~> 1.2) - multi_json (~> 1.0) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.2.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - sprockets (>= 2.8, < 4.0) - thin (1.6.2) - daemons (>= 1.0.9) - eventmachine (>= 1.0.0) - rack (>= 1.0.0) - thor (0.19.1) - thread_safe (0.3.4) - tilt (1.4.1) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.0) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thin (1.7.0) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (>= 1, < 3) + thor (0.19.4) + thread_safe (0.3.5) + tilt (2.0.6) tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (2.5.3) - execjs (>= 0.3.0) - json (>= 1.8.0) + uglifier (3.0.4) + execjs (>= 0.3.0, < 3) vcr (2.9.3) webmock (1.19.0) addressable (>= 2.3.6) crack (>= 0.3.2) - will_paginate (3.0.7) + will_paginate (3.0.12) PLATFORMS ruby DEPENDENCIES - compass (~> 1.0.1) + bootstrap-sass (~> 3.3.7) + compass-rails (~> 3.0.2) countries (~> 0.9.3) database_cleaner (~> 1.5.1) factory_girl_rails (~> 4.4.1) faker (~> 1.6.1) geoip (~> 1.4.0) geokit-rails (~> 2.0.1) + jasmine-rails (~> 0.14.1) jbuilder (~> 2.0) pg (~> 0.17.1) rails (~> 4.2.0) + react-rails (~> 1.10.0) responders (~> 2.1.1) rspec-collection_matchers (~> 1.1.2) rspec-rails (~> 3.4.0) - sass (~> 3.4.5) + sass-rails (~> 5.0.6) sdoc (~> 0.4.0) spring spring-commands-rspec - thin (~> 1.6.2) + thin (~> 1.7.0) uglifier (>= 1.3.0) vcr (~> 2.9.3) webmock (~> 1.19.0) will_paginate (~> 3.0.7) +RUBY VERSION + ruby 2.2.4p230 + BUNDLED WITH - 1.11.2 + 1.13.6 diff --git a/README.md b/README.md index d36d89d..b557bcb 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # BlicblockJS -Blicblock is a game your Sims in The Sims 4 can play on the computer. I thought -it would be fun to recreate the game. Our Sims shouldn't have all the fun! +[![Build Status](https://travis-ci.org/cheshire137/blicblock-js.svg?branch=master)](https://travis-ci.org/cheshire137/blicblock-js) + +Blicblock is a game that Sims in The Sims 4 play on their computers. I thought +it would be fun to recreate the game; our Sims shouldn't have all the fun! ![BlicblockJS gameplay](https://raw.githubusercontent.com/cheshire137/blicblock-js/master/blicblockjs-screenshot-1.png) -BlickblockJS is built using AngularJS, Bower, Yeoman, and Twitter Bootstrap. +BlickblockJS is built using ReactJS and Ruby on Rails. ## Blicblock Notes from The Sims 4 @@ -18,42 +20,28 @@ BlickblockJS is built using AngularJS, Bower, Yeoman, and Twitter Bootstrap. ## How to Run -You need Ruby, RubyGems, Bundler, Node.js, and PostgreSQL. - -1. `bundle` -1. Create a `blicblockjs` role in PostgreSQL: - - % psql - psql (9.2.1) - Type "help" for help. +You need Ruby, RubyGems, Bundler, and PostgreSQL. - sarah=# CREATE USER blicblockjs WITH PASSWORD 'password'; - CREATE ROLE - sarah=# ALTER USER blicblockjs WITH SUPERUSER; - ALTER ROLE - - Or via command line: `createuser -P -s -e blicblockjs` - -1. `bundle exec rake db:create db:migrate db:seed` -1. `cd client/` -1. `npm install` -1. `npm install -g bower` -1. `bower install` -1. `npm install -g grunt-cli` -1. `grunt serve` to watch for file changes and to launch the Rails server. +```bash +bundle +bundle exec rake db:create db:migrate db:seed +bundle exec rails s +open http://localhost:3000 +``` ## How to Test -### Rails API - -1. `RAILS_ENV=test bundle exec rake db:create db:migrate` -1. `RAILS_ENV=test bundle exec rspec` +```bash +RAILS_ENV=test bundle exec rake db:create db:migrate +RAILS_ENV=test bundle exec rake spec +``` -### AngularJS +You can also run Jasmine JavaScript tests in the browser: -1. `cd client/` -1. `npm install` -1. `grunt test` +```bash +bundle exec rails s +open http://localhost:3000/specs +``` ## How to Deploy to Heroku diff --git a/public/images/blank.43deb719.gif b/app/assets/images/blank.gif similarity index 100% rename from public/images/blank.43deb719.gif rename to app/assets/images/blank.gif diff --git a/public/images/blicblock-screenshot-1.ff391b61.png b/app/assets/images/blicblock-screenshot-1.png similarity index 100% rename from public/images/blicblock-screenshot-1.ff391b61.png rename to app/assets/images/blicblock-screenshot-1.png diff --git a/public/images/blicblock-screenshot-2.61427e17.png b/app/assets/images/blicblock-screenshot-2.png similarity index 100% rename from public/images/blicblock-screenshot-2.61427e17.png rename to app/assets/images/blicblock-screenshot-2.png diff --git a/public/images/flags.65f87fe2.png b/app/assets/images/flags.png similarity index 100% rename from public/images/flags.65f87fe2.png rename to app/assets/images/flags.png diff --git a/public/images/yeoman.d2754b85.png b/app/assets/images/yeoman.png similarity index 100% rename from public/images/yeoman.d2754b85.png rename to app/assets/images/yeoman.png diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000..222a677 --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,4 @@ +//= require react +//= require react_ujs +//= require_tree ./models +//= require_tree ./components diff --git a/app/assets/javascripts/components/.gitkeep b/app/assets/javascripts/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/components/app.es6.jsx b/app/assets/javascripts/components/app.es6.jsx new file mode 100644 index 0000000..c107124 --- /dev/null +++ b/app/assets/javascripts/components/app.es6.jsx @@ -0,0 +1,12 @@ +class App extends React.Component { + constructor() { + super() + this.state = { view: 'board' } + } + + render () { + switch (this.state.view) { + default: return + } + } +} diff --git a/app/assets/javascripts/components/block_preview.es6.jsx b/app/assets/javascripts/components/block_preview.es6.jsx new file mode 100644 index 0000000..175e5b7 --- /dev/null +++ b/app/assets/javascripts/components/block_preview.es6.jsx @@ -0,0 +1,9 @@ +class BlockPreview extends React.Component { + render () { + return ( +
+
+ ) + } +} + diff --git a/app/assets/javascripts/components/board.es6.jsx b/app/assets/javascripts/components/board.es6.jsx new file mode 100644 index 0000000..13f4336 --- /dev/null +++ b/app/assets/javascripts/components/board.es6.jsx @@ -0,0 +1,27 @@ +class Board extends React.Component { + blockClass(block) { + const classes = ['animate', 'block', block.color, + `pos-${block.x}${block.y}`] + if (block.highlight) { + classes.push('glow') + } + return classes.join(' ') + } + + render () { + return ( +
+ {this.props.blocks.map(block => { + const key = `${block.id}-${block.x}-${block.y}-${block.sliding}-` + + `${block.plummetting}-${block.highlight}-${block.active}-` + + `${block.color}-${block.locked}` + return
+ })} +
+ ) + } +} + +Board.propTypes = { + blocks: React.PropTypes.array.isRequired, +} diff --git a/app/assets/javascripts/components/board_container.es6.jsx b/app/assets/javascripts/components/board_container.es6.jsx new file mode 100644 index 0000000..933f8cb --- /dev/null +++ b/app/assets/javascripts/components/board_container.es6.jsx @@ -0,0 +1,562 @@ +const COLS = 5 +const ROWS = 7 +const MIDDLE_COL_IDX = (COLS - 1) / 2 +const INITIAL_TICK_LENGTH = 850 +const HAVE_LOCAL_STORAGE = LocalStorage.isAvailable() +const SCORE_VALUE = 1000 +const POINTS_PER_LEVEL = 4000 + +class BoardContainer extends React.Component { + constructor() { + super() + this.state = { + inProgress: true, + gameOver: false, + currentScore: 0, + level: 1, + submittedScore: false, + testMode: false, + blocks: [], + upcoming: [new Block(), new Block()], + tickLength: INITIAL_TICK_LENGTH, // ms + checking: false, + plummettingBlock: false, + slidingBlock: false, + } + this.onKeyUp = this.onKeyUp.bind(this) + } + + componentDidMount() { + document.addEventListener('keyup', this.onKeyUp) + this.startGameInterval() + } + + componentWillUnmount() { + this.cancelGameInterval() + document.removeEventListener('keyup', this.onKeyUp) + } + + onKeyUp(event) { + if (event.which === 32) { // Space + this.togglePause() + } else if (event.which === 37) { // Left arrow + this.moveLeft() + } else if (event.which === 39) { // Right arrow + this.moveRight() + } else if (event.which === 40) { // Down arrow + this.moveDown() + } + } + + getUpdatedBlocks(idx, block) { + const { blocks } = this.state + return blocks.slice(0, idx).concat([block]).concat(blocks.slice(idx + 1)) + } + + stopSliding(id) { + const block = this.getBlockByID(id) + const index = this.getBlockIndex(block) + const attrs = block.attrs() + attrs.sliding = false + const newBlock = new Block(attrs) + const blocks = this.getUpdatedBlocks(index, newBlock) + this.setState({ blocks, slidingBlock: false }) + } + + getBlockIndex(block) { + const ids = this.state.blocks.map(b => b.id) + return ids.indexOf(block.id) + } + + moveLeft() { + if (!this.state.inProgress) { + return + } + const block = this.getActiveBlock() + if (!block || block.plummetting || block.sliding || block.y === 0) { + return + } + const index = this.getBlockIndex(block) + const attrs = block.attrs() + let slidingBlock = false + attrs.sliding = true + const blockToLeft = this.getClosestBlockToLeft(attrs.x, attrs.y) + if (blockToLeft && blockToLeft.y === attrs.y - 1) { + attrs.sliding = false + } else { + attrs.y-- + setTimeout(() => this.stopSliding(block.id), 100) + slidingBlock = true + } + const newBlock = new Block(attrs) + const blocks = this.getUpdatedBlocks(index, newBlock) + this.setState({ blocks, slidingBlock }) + } + + moveRight() { + if (!this.state.inProgress) { + return + } + const block = this.getActiveBlock() + if (!block || block.plummetting || block.sliding || block.y === COLS - 1) { + return + } + const index = this.getBlockIndex(block) + const attrs = block.attrs() + let slidingBlock = false + attrs.sliding = true + const blockToRight = this.getClosestBlockToRight(attrs.x, attrs.y) + if (blockToRight && blockToRight.y === attrs.y + 1) { + attrs.sliding = false + } else { + attrs.y++ + setTimeout(() => this.stopSliding(block.id), 100) + slidingBlock = true + } + const newBlock = new Block(attrs) + const blocks = this.getUpdatedBlocks(index, newBlock) + this.setState({ blocks, slidingBlock }) + } + + moveDown() { + if (!this.state.inProgress) { + return + } + const block = this.getActiveBlock() + if (!block || block.plummetting || block.sliding || block.x == ROWS - 1) { + return + } + const blockBelow = this.getClosestBlockBelow(block.x, block.y) + const newX = blockBelow ? blockBelow.x - 1 : ROWS - 1 + this.plummetBlock(block, newX).then(() => { + this.cancelGameInterval() + this.dropQueuedBlockIfNoActive() + this.startGameInterval() + }) + } + + getBlockByID(id) { + return this.state.blocks.filter(b => b.id === id)[0] + } + + plummetBlock(originalBlock, x) { + return new Promise(resolve => { + if (originalBlock.x === x) { + resolve() + } else { + let interval = undefined + dropSingleBlock = (id) => { + const block = this.getBlockByID(id) + const index = this.getBlockIndex(block) + const attrs = block.attrs() + attrs.plummetting = true + + if (attrs.x < x) { + attrs.x++ + const newBlock = new Block(attrs) + const blocks = this.getUpdatedBlocks(index, newBlock) + this.setState({ blocks, plummettingBlock: true }) + } else if (attrs.x === x) { + clearInterval(interval) + attrs.locked = true + attrs.active = false + attrs.plummetting = false + const newBlock = new Block(attrs) + const blocks = this.getUpdatedBlocks(index, newBlock) + this.setState({ blocks, plummettingBlock: false }, () => { + this.onBlockLand(newBlock.id) + resolve() + }) + } + } + dropSingleBlock(originalBlock.id) + interval = setInterval(() => dropSingleBlock(originalBlock.id), 25) + } + }) + } + + deHighlightBlock(id) { + const block = this.getBlockByID(id) + if (!block) { + return + } + const attrs = block.attrs() + attrs.highlight = false + const index = this.getBlockIndex(block) + const newBlock = new Block(attrs) + this.setState({ blocks: this.getUpdatedBlocks(index, newBlock) }) + } + + onBlockLand(id) { + const block = this.getBlockByID(id) + if (!block) { + return + } + const attrs = block.attrs() + attrs.highlight = true + const index = this.getBlockIndex(block) + const newBlock = new Block(attrs) + this.setState({ blocks: this.getUpdatedBlocks(index, newBlock) }, () => { + setTimeout(() => this.deHighlightBlock(id), this.state.tickLength * 0.21) + this.checkForTetrominos() + }) + } + + togglePause() { + if (this.state.inProgress) { + this.pauseGame() + } else { + this.resumeGame() + } + } + + gameLoop() { + const { inProgress, plummettingBlock, slidingBlock } = this.state + if (!inProgress) { + return + } + if (plummettingBlock || slidingBlock) { + return + } + this.dropBlocks() + this.dropQueuedBlockIfNoActive() + } + + getClosestBlockBelow(x, y) { + const blocksBelow = this.state.blocks.filter(b => b.x > x && b.y === y) + blocksBelow.sort((a, b) => { + if (a.x < b.x) { + return -1 + } + return a.x > b.x ? 1 : 0 + }) + return blocksBelow[0] + } + + getClosestBlockToRight(x, y) { + const blocksToRight = this.state.blocks.filter(b => b.x === x && b.y > y) + blocksToRight.sort((a, b) => { + if (a.y < b.y) { + return -1 + } + return a.y > b.y ? 1 : 0 + }) + return blocksToRight[0] + } + + getClosestBlockToLeft(x, y) { + const blocksToLeft = this.state.blocks.filter(b => b.x === x && b.y < y) + blocksToLeft.sort((a, b) => { + if (a.y < b.y) { + return -1 + } + return a.y > b.y ? 1 : 0 + }) + } + + getBlocksOnTop(blocks) { + const xCoords = blocks.map(b => b.x) + const yCoords = blocks.map(b => b.y) + const maxX = Math.max.apply(null, xCoords) + const blocksOnTop = this.state.blocks.filter(b => + b.x < maxX && yCoords.indexOf(b.y) > -1 + ) + blocksOnTop.sort((a, b) => { // Closest to bottom first + if (a.x < b.x) { + return 1 + } + return a.x > b.x ? -1 : 0 + }) + return blocksOnTop + } + + dropBlocks() { + const lastRowX = ROWS - 1 + const landingBlockIDs = [] + const blocks = this.state.blocks.map(block => { + if (block.sliding) { + return block + } + const attrs = block.attrs() + if (attrs.active || !attrs.locked) { + if (attrs.x === lastRowX) { + attrs.locked = true + attrs.active = false + attrs.highlight = true + landingBlockIDs.push(attrs.id) + } + if (this.isBlockDirectlyBelow(attrs.x, attrs.y)) { + attrs.locked = true + attrs.active = false + landingBlockIDs.push(attrs.id) + } + } + if (!attrs.locked) { + attrs.x++ + } + return new Block(attrs) + }) + this.setState({ blocks }, () => { + landingBlockIDs.forEach(id => { + this.onBlockLand(id) + }) + }) + } + + isBlockDirectlyBelow(x, y) { + const matching = this.state.blocks.filter(block => { + return block.x === x + 1 && block.y === y + }) + return matching.length > 0 + } + + // Find a block at the given position with the given color. + lookup(x, y, color) { + if (x >= ROWS || y >= COLS) { + return + } + return this.state.blocks.filter(b => + b.x === x && b.y === y && b.color === color + )[0] + } + + removeBlocks(blocksToRemove) { + return new Promise(resolve => { + const idsToRemove = blocksToRemove.map(b => b.id) + const blocks = this.state.blocks.concat([]) + let index = blocks.length - 1 + while (index >= 0) { + if (idsToRemove.indexOf(blocks[index].id) > -1) { + blocks.splice(index, 1) + } + index-- + } + const currentScore = this.state.currentScore + SCORE_VALUE + let level = this.state.level + if (currentScore % POINTS_PER_LEVEL === 0) { + level++ + } + const blocksOnTop = this.getBlocksOnTop(blocksToRemove). + filter(b => !b.active) + const newState = { level, currentScore, blocks } + if (blocksOnTop.length > 0) { + this.setState(newState, () => { + this.plummetBlocks(blocksOnTop.map(b => b.id)).then(() => resolve()) + }) + } else { + this.setState(newState, () => resolve()) + } + }) + } + + plummetBlocks(blockIDs) { + return new Promise(resolve => { + const afterward = () => { + if (blockIDs.length > 1) { + this.plummetBlocks(blockIDs.slice(1)).then(() => resolve()) + } else { + resolve() + } + } + const block = this.getBlockByID(blockIDs[0]) + if (block) { + const blockBelow = this.getClosestBlockBelow(block.x, block.y) + const newX = blockBelow ? blockBelow.x - 1 : ROWS - 1 + this.plummetBlock(block, newX).then(afterward) + } else { + afterward() + } + }) + } + + checkForTetrominos() { + if (!this.state.inProgress || this.state.checking) { + return + } + this.setState({ checking: true }, () => { + const promises = [] + this.state.blocks.forEach(block => { + if (block && block.locked && !block.active) { + promises.push(this.checkForTetrominoAtBlock(block.id)) + } + }) + Promise.all(promises).then(() => this.setState({ checking: false })) + }) + } + + checkForTetrominoAtBlock(id) { + return new Promise(resolve => { + const block = this.getBlockByID(id) + const index = this.getBlockIndex(block) + const checker = new TetrominoChecker(this.state.blocks, index) + if (checker.check()) { + this.removeBlocks(checker.tetromino).then(() => resolve()) + } else { + resolve() + } + }) + } + + getActiveBlock() { + return this.state.blocks.filter(block => block.active)[0] + } + + dropQueuedBlockIfNoActive() { + if (!this.getActiveBlock()) { + this.dropQueuedBlock() + } + } + + dropQueuedBlock() { + if (this.state.checking) { + return + } + const middleColBlocks = this.state.blocks.filter(block => { + return block.y === MIDDLE_COL_IDX + }) + if (middleColBlocks.length >= ROWS) { + this.gameOver() + return + } + const x = 0 + const y = MIDDLE_COL_IDX + const topMidBlock = this.state.blocks.filter(block => { + return block.x === x && block.y === y + })[0] + if (topMidBlock) { + return // Currently dropping or sliding at the top + } + const attrs = this.state.upcoming[0].attrs() + attrs.x = x + attrs.y = y + const block = new Block(attrs) + const upcoming = [this.state.upcoming[1], new Block()] + this.setState({ upcoming, blocks: this.state.blocks.concat([block]) }) + } + + gameOver() { + this.setState({ inProgress: false, gameOver: true }) + this.cancelGameInterval() + this.saveHighScore() + } + + startGameInterval() { + if (typeof this.state.gameInterval !== 'undefined') { + return + } + const gameInterval = setInterval(() => this.gameLoop(), + this.state.tickLength) + this.setState({ gameInterval }) + } + + cancelGameInterval() { + clearInterval(this.state.gameInterval) + this.setState({ gameInterval: undefined }) + } + + saveHighScore() { + const { currentScore, testMode } = this.state + if (testMode || !HAVE_LOCAL_STORAGE || currentScore <= 0) { + return + } + const existingHighScore = this.getExistingHighScore() + if (!existingHighScore.value || existingHighScore.value < currentScore) { + const score = { + value: currentScore, + initials: existingHighScore.initials, + date: new Date(), + } + LocalStorage.set('high_score', score) + } + } + + containerClass() { + const classes = ['board-container'] + if (this.state.testMode) { + classes.push('test-mode') + } + if (this.state.gameOver) { + classes.push('game-over') + } + if (this.state.inProgress) { + classes.push('in-progress') + } else { + classes.push('paused') + } + return classes.join(' ') + } + + getExistingHighScore() { + if (!HAVE_LOCAL_STORAGE) { + return {} + } + return LocalStorage.get('high_score') || {} + } + + getNewHighScore(existingHighScore) { + const { currentScore } = this.state + const newHighScore = {} + if (existingHighScore.value && currentScore > existingHighScore.value) { + newHighScore.value = currentScore + } + return newHighScore + } + + startNewGame() { + this.cancelGameInterval() + const gameInterval = setInterval(() => this.gameLoop(), + INITIAL_TICK_LENGTH) + this.setState({ + gameInterval, + inProgress: true, + gameOver: false, + currentScore: 0, + level: 1, + submittedScore: false, + blocks: [], + upcoming: [new Block(), new Block()], + tickLength: INITIAL_TICK_LENGTH, // ms + checking: false, + }) + } + + resumeGame() { + if (this.state.gameOver) { + return + } + this.setState({ inProgress: true }, () => this.startGameInterval()) + } + + pauseGame() { + if (this.state.plummettingBlock) { + return + } + this.setState({ inProgress: false }, () => this.cancelGameInterval()) + } + + render () { + const { currentScore, level, inProgress, gameOver, submittedScore, + testMode, blocks } = this.state + const existingHighScore = this.getExistingHighScore() + const newHighScore = this.getNewHighScore(existingHighScore) + return ( +
+
{currentScore}
+
{level}
+ + + this.startNewGame()} + resumeGame={() => this.resumeGame()} + /> +
+ ) + } +} diff --git a/app/assets/javascripts/components/game_message.es6.jsx b/app/assets/javascripts/components/game_message.es6.jsx new file mode 100644 index 0000000..66edd69 --- /dev/null +++ b/app/assets/javascripts/components/game_message.es6.jsx @@ -0,0 +1,118 @@ +class GameMessage extends React.Component { + existingHighScore() { + const { newHighScore, existingHighScore } = this.props + if (newHighScore.value || !existingHighScore.value) { + return null + } + return ( +
+ Your high score: + {existingHighScore.value} + +
+ ) + } + + newHighScore() { + const { newHighScore } = this.props + if (!newHighScore.value) { + return + } + return ( +
+ New personal high score! +
+ ) + } + + testMode() { + const { testMode } = this.props + if (!testMode) { + return null + } + return ( +
+ Test mode, score is not saved. +
+ ) + } + + submitScoreForm() { + const { currentScore, testMode, submittedScore } = this.props + if (currentScore <= 0 || testMode || submittedScore) { + return null + } + return ( +
+ + +
+ ) + } + + gameOver() { + const { currentScore } = this.props + return ( +
+

game over

+
+ Final score: {currentScore} +
+ {this.submitScoreForm()} + {this.testMode()} + {this.newHighScore()} + {this.existingHighScore()} + +
+ ) + } + + paused() { + return ( +
+

paused

+ {this.testMode()} + +
+ ) + } + + render () { + const { inProgress, gameOver } = this.props + if (inProgress) { + return null + } + if (gameOver) { + return this.gameOver() + } + return this.paused() + } +} + +GameMessage.propTypes = { + inProgress: React.PropTypes.bool.isRequired, + gameOver: React.PropTypes.bool.isRequired, + testMode: React.PropTypes.bool.isRequired, + submittedScore: React.PropTypes.bool.isRequired, + currentScore: React.PropTypes.number.isRequired, + existingHighScore: React.PropTypes.object, + newHighScore: React.PropTypes.object, + startNewGame: React.PropTypes.func.isRequired, + resumeGame: React.PropTypes.func.isRequired, +} diff --git a/app/assets/javascripts/models/block.es6.js b/app/assets/javascripts/models/block.es6.js new file mode 100644 index 0000000..d3f07f0 --- /dev/null +++ b/app/assets/javascripts/models/block.es6.js @@ -0,0 +1,42 @@ +class Block { + constructor(attrs) { + attrs = attrs || {} + if (typeof attrs.color === 'string') { + this.color = attrs.color + } else { + const colors = ['magenta', 'orange', 'yellow', 'green', 'blue', 'white'] + this.color = colors[Math.floor(Math.random() * colors.length)] + } + this.x = attrs.x + this.y = attrs.y + if (typeof attrs.locked === 'undefined') { + this.locked = false + } else { + this.locked = attrs.locked + } + if (typeof attrs.active === 'undefined') { + this.active = true + } else { + this.active = attrs.active + } + this.sliding = attrs.sliding || false + this.plummetting = attrs.plummetting || false + this.highlight = attrs.highlight || false + this.id = attrs.id || this.generateID() + } + + generateID() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = Math.random() * 16 | 0 + const v = c === 'x' ? r : (r & 0x3 | 0x8) + return v.toString(16) + }) + } + + attrs() { + return { color: this.color, x: this.x, y: this.y, locked: this.locked, + active: this.active, sliding: this.sliding, + plummetting: this.plummetting, highlight: this.highlight, + id: this.id } + } +} diff --git a/app/assets/javascripts/models/local_storage.es6.js b/app/assets/javascripts/models/local_storage.es6.js new file mode 100644 index 0000000..7b827da --- /dev/null +++ b/app/assets/javascripts/models/local_storage.es6.js @@ -0,0 +1,29 @@ +class LocalStorage { + static isAvailable() { + try { + const storage = window.localStorage + const x = '__storage_test__' + storage.setItem(x, x) + storage.removeItem(x) + return true + } catch(e) { + return false + } + } + + static set(key, value) { + window.localStorage.setItem(key, JSON.stringify(value)) + } + + static get(key) { + const value = window.localStorage.getItem(key) + if (typeof value === 'string') { + return JSON.parse(value) + } + return value + } + + static delete(key) { + window.localStorage.removeItem(key) + } +} diff --git a/app/assets/javascripts/models/tetromino_checker.es6.js b/app/assets/javascripts/models/tetromino_checker.es6.js new file mode 100644 index 0000000..3700f7a --- /dev/null +++ b/app/assets/javascripts/models/tetromino_checker.es6.js @@ -0,0 +1,35 @@ +class TetrominoChecker { + constructor(blocks, index) { + this.index = index + this.block1 = blocks[this.index] + this.blocks = blocks.filter(b => { + return b.color === this.block1.color && b.id !== this.block1.id + }) + this.tetromino = [] + } + + // Returns true if the block at the given index is part of a tetromino. Sets + // this.tetromino to an array containing the blocks that make up the + // tetromino. + check() { + if (this.blocks.length < 1) { + return false + } + this.blocks.forEach(block => { + const xDiff = Math.abs(block.x - this.block1.x) + const yDiff = Math.abs(block.y - this.block1.y) + if (block.x === this.block1.x && yDiff <= 3) { + this.tetromino.push(block) + } else if (block.y === this.block1.y && xDiff <= 3) { + this.tetromino.push(block) + } else if (xDiff <= 2 && yDiff <= 1 || xDiff <= 1 && yDiff <= 2) { + this.tetromino.push(block) + } + }) + if (this.tetromino.length === 3) { + this.tetromino.push(this.block1) + return true + } + return false + } +} diff --git a/client/app/styles/_flags.scss b/app/assets/stylesheets/_flags.css.scss similarity index 99% rename from client/app/styles/_flags.scss rename to app/assets/stylesheets/_flags.css.scss index eaf9c3c..fc7249d 100644 --- a/client/app/styles/_flags.scss +++ b/app/assets/stylesheets/_flags.css.scss @@ -3,7 +3,7 @@ height: 11px; display: inline-block; position: relative; - background: url("../images/flags.png") no-repeat; + background: image-url("flags.png") no-repeat; } .flag.flag-ad {background-position: -16px 0} diff --git a/app/assets/stylesheets/_fonts.css.scss b/app/assets/stylesheets/_fonts.css.scss new file mode 100644 index 0000000..f6e3002 --- /dev/null +++ b/app/assets/stylesheets/_fonts.css.scss @@ -0,0 +1,23 @@ +@font-face { + font-family: 'handwrittensimlishfontmedium'; + src: url('fonts/handwrittensimlishfont-webfont.eot'); + src: url('fonts/handwrittensimlishfont-webfont.eot?#iefix') format('embedded-opentype'), + url('fonts/handwrittensimlishfont-webfont.woff2') format('woff2'), + url('fonts/handwrittensimlishfont-webfont.woff') format('woff'), + url('fonts/handwrittensimlishfont-webfont.ttf') format('truetype'), + url('fonts/handwrittensimlishfont-webfont.svg#handwrittensimlishfontmedium') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'press_start_2pregular'; + src: url('fonts/pressstart2p-webfont.eot'); + src: url('fonts/pressstart2p-webfont.eot?#iefix') format('embedded-opentype'), + url('fonts/pressstart2p-webfont.woff2') format('woff2'), + url('fonts/pressstart2p-webfont.woff') format('woff'), + url('fonts/pressstart2p-webfont.ttf') format('truetype'), + url('fonts/pressstart2p-webfont.svg#press_start_2pregular') format('svg'); + font-weight: normal; + font-style: normal; +} diff --git a/app/assets/stylesheets/_variables.css.scss b/app/assets/stylesheets/_variables.css.scss new file mode 100644 index 0000000..b229c1f --- /dev/null +++ b/app/assets/stylesheets/_variables.css.scss @@ -0,0 +1,34 @@ +$block-width: 65px; +$block-height: $block-width; +$preview-block-width: 22px; +$preview-block-height: $preview-block-width; +$block-magenta: #AF197C; +$block-yellow: #B7A45D; +$block-green: #6EAB34; +$block-blue: #439CAE; +$block-white: #B0B0B0; +$block-orange: #AF6D19; +$block-stripe-count: 4.5; +$block-stripe-darken-pct: 5%; +$rows: 7; +$columns: 5; +$screenshot-bg: #222; +$board-background-color: #111; +$board-border-width: 4px; +$board-border-color: #3A335D; +$board-border-radius: 20px; +$board-score-height: 80px; +$board-counter-width: 120px; +$board-counter-height: $board-counter-width; +$board-block-preview-padding: 0.4 * $block-width; +$board-block-preview-width: $block-width + ($board-block-preview-padding * 2); +$board-width: $columns * $block-width + ($board-border-width * 2); +$board-height: $rows * $block-height + ($board-border-width * 2); +$board-container-height: $board-height + $board-score-height - $board-border-width; +$mobile-block-width: 55px; +$mobile-block-height: $mobile-block-width; +$mobile-block-preview-width: (10px * 3) + ($mobile-block-height * 2) + ($board-border-width * 2); +$board-block-preview-height: ($board-block-preview-padding * 3) + ($block-height * 2) + ($board-border-width * 2); +$mobile-block-preview-padding: 10px; +$mobile-board-width: $columns * $mobile-block-width + ($board-border-width * 2); +$mobile-board-height: $rows * $mobile-block-height + ($board-border-width * 2); diff --git a/client/app/styles/main.scss b/app/assets/stylesheets/application.css.scss similarity index 91% rename from client/app/styles/main.scss rename to app/assets/stylesheets/application.css.scss index dfd1339..26d2936 100644 --- a/client/app/styles/main.scss +++ b/app/assets/stylesheets/application.css.scss @@ -1,47 +1,9 @@ -$icon-font-path: "../bower_components/bootstrap-sass-official/assets/fonts/bootstrap/"; -@import "fonts"; -@import "bootstrap"; @import "compass"; +@import "bootstrap-sprockets"; +@import "bootstrap"; +@import "fonts"; @import "flags"; - -[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { - display: none !important; -} - -$block-width: 65px; -$block-height: $block-width; -$preview-block-width: 22px; -$preview-block-height: $preview-block-width; -$block-magenta: #AF197C; -$block-yellow: #B7A45D; -$block-green: #6EAB34; -$block-blue: #439CAE; -$block-white: #B0B0B0; -$block-orange: #AF6D19; -$block-stripe-count: 4.5; -$block-stripe-darken-pct: 5%; -$rows: 7; -$columns: 5; -$screenshot-bg: #222; -$board-background-color: #111; -$board-border-width: 4px; -$board-border-color: #3A335D; -$board-border-radius: 20px; -$board-score-height: 80px; -$board-counter-width: 120px; -$board-counter-height: $board-counter-width; -$board-block-preview-padding: 0.4 * $block-width; -$board-block-preview-width: $block-width + ($board-block-preview-padding * 2); -$board-width: $columns * $block-width + ($board-border-width * 2); -$board-height: $rows * $block-height + ($board-border-width * 2); -$board-container-height: $board-height + $board-score-height - $board-border-width; -$mobile-block-width: 55px; -$mobile-block-height: $mobile-block-width; -$mobile-block-preview-width: (10px * 3) + ($mobile-block-height * 2) + ($board-border-width * 2); -$board-block-preview-height: ($board-block-preview-padding * 3) + ($block-height * 2) + ($board-border-width * 2); -$mobile-block-preview-padding: 10px; -$mobile-board-width: $columns * $mobile-block-width + ($board-border-width * 2); -$mobile-board-height: $rows * $mobile-block-height + ($board-border-width * 2); +@import "variables"; a, .btn { -webkit-transition: all 0.25s ease-in-out; @@ -57,10 +19,6 @@ a { } } -a[ng-click], a[clip-click] { - cursor: pointer; -} - .animate { &.ng-hide-add, &.ng-hide-remove { @@ -288,75 +246,71 @@ code { } .game-message { - display: none; + text-align: center; + display: block; + position: absolute; + z-index: 1; + left: 0; + width: 100%; + top: 0; + height: $board-container-height; + background-color: rgba(0, 0, 0, 0.7); - &.active { - text-align: center; - display: block; + & > .btn { position: absolute; - z-index: 1; - left: 0; - width: 100%; - top: 0; - height: $board-container-height; - background-color: rgba(0, 0, 0, 0.7); + bottom: 50px; + left: 50%; + } - & > .btn { - position: absolute; - bottom: 50px; - left: 50%; + .submit-score-form { + margin: 0 40px; + + input[type="text"] { + width: 8em; } + } - .submit-score-form { - margin: 0 40px; + h2 { + color: #A58936; + text-shadow: -1px -1px 2px #8A743D, 0 0 0 #000, 1px 1px 2px #634A04; + letter-spacing: 0.05em; + font-size: 700%; + } - input[type="text"] { - width: 8em; - } - } + .test-mode-message { + font-size: $font-size-large; + margin: 40px; + } - h2 { - color: #A58936; - text-shadow: -1px -1px 2px #8A743D, 0 0 0 #000, 1px 1px 2px #634A04; - letter-spacing: 0.05em; - font-size: 700%; + &.game-over { + .new-game-button { + width: 7em; + margin-left: -3.5em; } - .test-mode-message { + .final-score-message, .new-high-score-message, + .existing-high-score-message { font-size: $font-size-large; - margin: 40px; + margin: 40px 0; } - &.game-over { - .new-game-button { - width: 7em; - margin-left: -3.5em; - } - - .final-score-message, .new-high-score-message, - .existing-high-score-message { - font-size: $font-size-large; - margin: 40px 0; - } - - .existing-high-score-message { - .date { - display: block; - font-size: $font-size-small; - margin-top: ($line-height-computed / 2); - color: #999; - } + .existing-high-score-message { + .date { + display: block; + font-size: $font-size-small; + margin-top: ($line-height-computed / 2); + color: #999; } } + } - &.paused { - background-color: #272153; - border-radius: 20px; + &.paused { + background-color: #272153; + border-radius: 20px; - .resume-game-button { - width: 9em; - margin-left: -4.5em; - } + .resume-game-button { + width: 9em; + margin-left: -4.5em; } } } diff --git a/client/app/styles/fonts/handwrittensimlishfont-webfont.eot b/app/assets/stylesheets/fonts/handwrittensimlishfont-webfont.eot old mode 100755 new mode 100644 similarity index 100% rename from client/app/styles/fonts/handwrittensimlishfont-webfont.eot rename to app/assets/stylesheets/fonts/handwrittensimlishfont-webfont.eot diff --git a/client/app/styles/fonts/handwrittensimlishfont-webfont.svg b/app/assets/stylesheets/fonts/handwrittensimlishfont-webfont.svg old mode 100755 new mode 100644 similarity index 100% rename from client/app/styles/fonts/handwrittensimlishfont-webfont.svg rename to app/assets/stylesheets/fonts/handwrittensimlishfont-webfont.svg diff --git a/client/app/styles/fonts/handwrittensimlishfont-webfont.ttf b/app/assets/stylesheets/fonts/handwrittensimlishfont-webfont.ttf old mode 100755 new mode 100644 similarity index 100% rename from client/app/styles/fonts/handwrittensimlishfont-webfont.ttf rename to app/assets/stylesheets/fonts/handwrittensimlishfont-webfont.ttf diff --git a/client/app/styles/fonts/handwrittensimlishfont-webfont.woff b/app/assets/stylesheets/fonts/handwrittensimlishfont-webfont.woff old mode 100755 new mode 100644 similarity index 100% rename from client/app/styles/fonts/handwrittensimlishfont-webfont.woff rename to app/assets/stylesheets/fonts/handwrittensimlishfont-webfont.woff diff --git a/client/app/styles/fonts/handwrittensimlishfont-webfont.woff2 b/app/assets/stylesheets/fonts/handwrittensimlishfont-webfont.woff2 old mode 100755 new mode 100644 similarity index 100% rename from client/app/styles/fonts/handwrittensimlishfont-webfont.woff2 rename to app/assets/stylesheets/fonts/handwrittensimlishfont-webfont.woff2 diff --git a/client/app/styles/fonts/pressstart2p-webfont.eot b/app/assets/stylesheets/fonts/pressstart2p-webfont.eot old mode 100755 new mode 100644 similarity index 100% rename from client/app/styles/fonts/pressstart2p-webfont.eot rename to app/assets/stylesheets/fonts/pressstart2p-webfont.eot diff --git a/client/app/styles/fonts/pressstart2p-webfont.svg b/app/assets/stylesheets/fonts/pressstart2p-webfont.svg old mode 100755 new mode 100644 similarity index 100% rename from client/app/styles/fonts/pressstart2p-webfont.svg rename to app/assets/stylesheets/fonts/pressstart2p-webfont.svg diff --git a/client/app/styles/fonts/pressstart2p-webfont.ttf b/app/assets/stylesheets/fonts/pressstart2p-webfont.ttf old mode 100755 new mode 100644 similarity index 100% rename from client/app/styles/fonts/pressstart2p-webfont.ttf rename to app/assets/stylesheets/fonts/pressstart2p-webfont.ttf diff --git a/client/app/styles/fonts/pressstart2p-webfont.woff b/app/assets/stylesheets/fonts/pressstart2p-webfont.woff old mode 100755 new mode 100644 similarity index 100% rename from client/app/styles/fonts/pressstart2p-webfont.woff rename to app/assets/stylesheets/fonts/pressstart2p-webfont.woff diff --git a/client/app/styles/fonts/pressstart2p-webfont.woff2 b/app/assets/stylesheets/fonts/pressstart2p-webfont.woff2 old mode 100755 new mode 100644 similarity index 100% rename from client/app/styles/fonts/pressstart2p-webfont.woff2 rename to app/assets/stylesheets/fonts/pressstart2p-webfont.woff2 diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb new file mode 100644 index 0000000..95f2992 --- /dev/null +++ b/app/controllers/home_controller.rb @@ -0,0 +1,4 @@ +class HomeController < ApplicationController + def index + end +end diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb new file mode 100644 index 0000000..093d50e --- /dev/null +++ b/app/views/home/index.html.erb @@ -0,0 +1 @@ +<%= react_component "App" %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000..984b43d --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,20 @@ + + + + + BlicblockJS + + + + + + <%= stylesheet_link_tag "application", media: "all" %> + <%= csrf_meta_tags %> + + +
+ <%= yield %> +
+ <%= javascript_include_tag "application" %> + + diff --git a/client/Gruntfile.js b/client/Gruntfile.js deleted file mode 100644 index 3b76b19..0000000 --- a/client/Gruntfile.js +++ /dev/null @@ -1,535 +0,0 @@ -// Generated on 2014-09-20 using generator-angular 0.9.5 -'use strict'; - -// # Globbing -// for performance reasons we're only matching one level down: -// 'test/spec/{,*/}*.js' -// use this if you want to recursively match all subfolders: -// 'test/spec/**/*.js' - -module.exports = function (grunt) { - - // Load grunt tasks automatically - require('load-grunt-tasks')(grunt); - - // Time how long tasks take. Can help when optimizing build times - require('time-grunt')(grunt); - - // Configurable paths for the application - var appConfig = { - app: require('./bower.json').appPath || 'app', - dist: '../public' - }; - - // Define the configuration for all the tasks - grunt.initConfig({ - - // Project settings - yeoman: appConfig, - - shell: { - startRailsServer: { - command: 'rails server', - options: { - // If async: true were omitted, the rails server - // command would prevent subsequent commands - // from running. - async: true - } - } - }, - - // Watches files for changes and runs tasks based on the changed files - watch: { - bower: { - files: ['bower.json'], - tasks: ['wiredep'] - }, - coffee: { - files: ['<%= yeoman.app %>/scripts/{,*/}*.{coffee,litcoffee,coffee.md}'], - tasks: ['newer:coffee:dist'] - }, - coffeeTest: { - files: ['test/spec/{,*/}*.{coffee,litcoffee,coffee.md}'], - tasks: ['newer:coffee:test', 'karma'] - }, - compass: { - files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], - tasks: ['compassMultiple', 'autoprefixer'] - }, - gruntfile: { - files: ['Gruntfile.js'] - }, - livereload: { - options: { - livereload: '<%= connect.options.livereload %>' - }, - files: [ - '<%= yeoman.app %>/{,*/}*.html', - '.tmp/styles/{,*/}*.css', - '.tmp/scripts/{,*/}*.js', - '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' - ] - } - }, - - // The actual grunt server settings - connect: { - options: { - port: 9000, - // Change this to '0.0.0.0' to access the server from outside. - hostname: '*', - livereload: 35729 - }, - proxies: [ - { - context: '/api', - host: 'localhost', - port: 3000 - } - ], - livereload: { - options: { - open: true, - middleware: function (connect, options) { - if (!Array.isArray(options.base)) { - options.base = [options.base]; - } - - // Setup the proxy - var middlewares = [ - require('grunt-connect-proxy/lib/utils').proxyRequest, - connect.static('.tmp'), - connect().use( - '/bower_components', - connect.static('./bower_components') - ), - connect.static(appConfig.app) - ]; - - options.base.forEach(function (base) { - middlewares.push(connect.static(base)); - }); - - // Make directory browse-able. - var directory = options.directory || - options.base[options.base.length - 1]; - middlewares.push(connect.directory(directory)); - - return middlewares; - } - } - }, - test: { - options: { - port: 9001, - middleware: function (connect) { - return [ - connect.static('.tmp'), - connect.static('test'), - connect().use( - '/bower_components', - connect.static('./bower_components') - ), - connect.static(appConfig.app) - ]; - } - } - }, - dist: { - options: { - open: true, - base: '<%= yeoman.dist %>' - } - } - }, - - // Make sure code styles are up to par and there are no obvious mistakes - jshint: { - options: { - jshintrc: '.jshintrc', - reporter: require('jshint-stylish') - }, - all: { - src: [ - 'Gruntfile.js' - ] - } - }, - - // Empties folders to start fresh - clean: { - options: {force: true}, - dist: { - files: [{ - dot: true, - src: [ - '.tmp', - '<%= yeoman.dist %>/{,*/}*', - '!<%= yeoman.dist %>/.git*' - ] - }] - }, - server: '.tmp' - }, - - // Add vendor prefixed styles - autoprefixer: { - options: { - browsers: ['last 1 version'] - }, - dist: { - files: [{ - expand: true, - cwd: '.tmp/styles/', - src: '{,*/}*.css', - dest: '.tmp/styles/' - }] - } - }, - - // Automatically inject Bower components into the app - wiredep: { - options: { - }, - app: { - src: ['<%= yeoman.app %>/index.html'], - ignorePath: /\.\.\// - }, - sass: { - src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], - ignorePath: /(\.\.\/){1,2}bower_components\// - } - }, - - // Compiles CoffeeScript to JavaScript - coffee: { - options: { - sourceMap: true, - sourceRoot: '' - }, - dist: { - files: [{ - expand: true, - cwd: '<%= yeoman.app %>/scripts', - src: '{,*/}*.coffee', - dest: '.tmp/scripts', - ext: '.js' - }] - }, - test: { - files: [{ - expand: true, - cwd: 'test/spec', - src: '{,*/}*.coffee', - dest: '.tmp/spec', - ext: '.js' - }] - } - }, - - compassMultiple: { - options: { - sassDir: '<%= yeoman.app %>/styles', - cssDir: '.tmp/styles', - generatedImagesDir: '.tmp/images/generated', - imagesDir: '<%= yeoman.app %>/images', - javascriptsDir: '<%= yeoman.app %>/scripts', - fontsDir: '<%= yeoman.app %>/styles/fonts', - importPath: './bower_components', - httpImagesPath: '/images', - httpGeneratedImagesPath: '/images/generated', - httpFontsPath: '/styles/fonts', - relativeAssets: false, - assetCacheBuster: false, - raw: 'Sass::Script::Number.precision = 10\n' - }, - dist: { - options: { - generatedImagesDir: '<%= yeoman.dist %>/images/generated' - } - }, - server: { - options: { - debugInfo: true - } - } - }, - - // Compiles Sass to CSS and generates necessary files if requested - compass: { - options: { - sassDir: '<%= yeoman.app %>/styles', - cssDir: '.tmp/styles', - generatedImagesDir: '.tmp/images/generated', - imagesDir: '<%= yeoman.app %>/images', - javascriptsDir: '<%= yeoman.app %>/scripts', - fontsDir: '<%= yeoman.app %>/styles/fonts', - importPath: './bower_components', - httpImagesPath: '/images', - httpGeneratedImagesPath: '/images/generated', - httpFontsPath: '/styles/fonts', - relativeAssets: false, - assetCacheBuster: false, - raw: 'Sass::Script::Number.precision = 10\n' - }, - dist: { - options: { - generatedImagesDir: '<%= yeoman.dist %>/images/generated' - } - }, - server: { - options: { - debugInfo: true - } - } - }, - - // Renames files for browser caching purposes - filerev: { - dist: { - src: [ - '<%= yeoman.dist %>/scripts/{,*/}*.js', - '<%= yeoman.dist %>/styles/{,*/}*.css', - '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' - ] - } - }, - - // Reads HTML for usemin blocks to enable smart builds that automatically - // concat, minify and revision files. Creates configurations in memory so - // additional tasks can operate on them - useminPrepare: { - html: '<%= yeoman.app %>/index.html', - options: { - dest: '<%= yeoman.dist %>', - flow: { - html: { - steps: { - js: ['concat', 'uglifyjs'], - css: ['cssmin'] - }, - post: {} - } - } - } - }, - - // Performs rewrites based on filerev and the useminPrepare configuration - usemin: { - html: ['<%= yeoman.dist %>/{,*/}*.html'], - css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], - options: { - assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images'] - } - }, - - // The following *-min tasks will produce minified files in the dist folder - // By default, your `index.html`'s will take care of - // minification. These next options are pre-configured if you do not wish - // to use the Usemin blocks. - // cssmin: { - // dist: { - // files: { - // '<%= yeoman.dist %>/styles/main.css': [ - // '.tmp/styles/{,*/}*.css' - // ] - // } - // } - // }, - // uglify: { - // dist: { - // files: { - // '<%= yeoman.dist %>/scripts/scripts.js': [ - // '<%= yeoman.dist %>/scripts/scripts.js' - // ] - // } - // } - // }, - // concat: { - // dist: {} - // }, - - imagemin: { - dist: { - files: [{ - expand: true, - cwd: '<%= yeoman.app %>/images', - src: '{,*/}*.{png,jpg,jpeg,gif}', - dest: '<%= yeoman.dist %>/images' - }] - } - }, - - svgmin: { - dist: { - files: [{ - expand: true, - cwd: '<%= yeoman.app %>/images', - src: '{,*/}*.svg', - dest: '<%= yeoman.dist %>/images' - }] - } - }, - - htmlmin: { - dist: { - options: { - collapseWhitespace: true, - conservativeCollapse: true, - collapseBooleanAttributes: true, - removeCommentsFromCDATA: true, - removeOptionalTags: true - }, - files: [{ - expand: true, - cwd: '<%= yeoman.dist %>', - src: ['*.html', 'views/{,*/}*.html'], - dest: '<%= yeoman.dist %>' - }] - } - }, - - // ngmin tries to make the code safe for minification automatically by - // using the Angular long form for dependency injection. It doesn't work on - // things like resolve or inject so those have to be done manually. - ngmin: { - dist: { - files: [{ - expand: true, - cwd: '.tmp/concat/scripts', - src: '*.js', - dest: '.tmp/concat/scripts' - }] - } - }, - - // Replace Google CDN references - cdnify: { - dist: { - html: ['<%= yeoman.dist %>/*.html'] - } - }, - - // Copies remaining files to places other tasks can use - copy: { - dist: { - files: [{ - expand: true, - dot: true, - cwd: '<%= yeoman.app %>', - dest: '<%= yeoman.dist %>', - src: [ - '*.{ico,png,txt}', - '.htaccess', - '*.html', - 'views/{,*/}*.html', - 'images/{,*/}*.{webp}', - 'styles/fonts/*' - ] - }, { - expand: true, - cwd: '.tmp/images', - dest: '<%= yeoman.dist %>/images', - src: ['generated/*'] - }, { - expand: true, - cwd: '.', - src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*', - dest: '<%= yeoman.dist %>' - }] - }, - styles: { - expand: true, - cwd: '<%= yeoman.app %>/styles', - dest: '.tmp/styles/', - src: '{,*/}*.css' - } - }, - - // Run some tasks in parallel to speed up the build process - concurrent: { - server: [ - 'coffee:dist', - 'compassMultiple' - ], - test: [ - 'coffee', - 'compass' - ], - dist: [ - 'coffee', - 'compass:dist', - 'imagemin', - 'svgmin' - ] - }, - - // Test settings - karma: { - unit: { - configFile: 'test/karma.conf.coffee', - singleRun: grunt.option('single-run') - } - } - }); - - - grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { - if (target === 'dist') { - return grunt.task.run(['build', 'connect:dist:keepalive']); - } - - grunt.task.run([ - 'clean:server', - 'wiredep', - 'concurrent:server', - 'shell:startRailsServer', - 'autoprefixer', - 'configureProxies:server', - 'connect:livereload', - 'watch' - ]); - }); - - grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) { - grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); - grunt.task.run(['serve:' + target]); - }); - - grunt.registerTask('test', [ - 'clean:server', - 'concurrent:test', - 'autoprefixer', - 'connect:test', - 'karma' - ]); - - grunt.registerTask('build', [ - 'clean:dist', - 'wiredep', - 'useminPrepare', - 'concurrent:dist', - 'autoprefixer', - 'concat', - 'ngmin', - 'copy:dist', - 'cdnify', - 'cssmin', - 'uglify', - 'filerev', - 'usemin', - 'htmlmin' - ]); - - grunt.registerTask('default', [ - 'newer:jshint', - 'test', - 'build' - ]); - - grunt.registerTask('heroku:production', 'build'); - - grunt.loadNpmTasks('grunt-connect-proxy'); - grunt.loadNpmTasks('grunt-shell-spawn'); -}; diff --git a/client/app/styles/_fonts.scss b/client/app/styles/_fonts.scss deleted file mode 100644 index 7841366..0000000 --- a/client/app/styles/_fonts.scss +++ /dev/null @@ -1,23 +0,0 @@ -@font-face { - font-family: 'handwrittensimlishfontmedium'; - src: url('fonts/handwrittensimlishfont-webfont.eot'); - src: url('fonts/handwrittensimlishfont-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/handwrittensimlishfont-webfont.woff2') format('woff2'), - url('fonts/handwrittensimlishfont-webfont.woff') format('woff'), - url('fonts/handwrittensimlishfont-webfont.ttf') format('truetype'), - url('fonts/handwrittensimlishfont-webfont.svg#handwrittensimlishfontmedium') format('svg'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'press_start_2pregular'; - src: url('fonts/pressstart2p-webfont.eot'); - src: url('fonts/pressstart2p-webfont.eot?#iefix') format('embedded-opentype'), - url('fonts/pressstart2p-webfont.woff2') format('woff2'), - url('fonts/pressstart2p-webfont.woff') format('woff'), - url('fonts/pressstart2p-webfont.ttf') format('truetype'), - url('fonts/pressstart2p-webfont.svg#press_start_2pregular') format('svg'); - font-weight: normal; - font-style: normal; -} diff --git a/client/app/styles/_variables.scss b/client/app/styles/_variables.scss deleted file mode 100644 index 98d2b65..0000000 --- a/client/app/styles/_variables.scss +++ /dev/null @@ -1,854 +0,0 @@ -// When true, asset path helpers are used, otherwise regular url() is used -// When there no function is defined, `fn('')` is parsed as string that equals the right hand side -// NB: in Sass 3.3 there is a native function: function-exists(twbs-font-path) -$bootstrap-sass-asset-helper: (twbs-font-path("") != unquote('twbs-font-path("")')) !default; - -// -// Variables -// -------------------------------------------------- - - -//== Colors -// -//## Gray and brand colors for use across Bootstrap. - -$gray-darker: lighten(#000, 13.5%) !default; // #222 -$gray-dark: lighten(#000, 20%) !default; // #333 -$gray: lighten(#000, 33.5%) !default; // #555 -$gray-light: lighten(#000, 46.7%) !default; // #777 -$gray-lighter: lighten(#000, 93.5%) !default; // #eee - -$brand-primary: #E24EE3 !default; -$brand-success: #669900 !default; -$brand-info: #33B5E5 !default; -$brand-warning: #FF8800 !default; -$brand-danger: #CC0000 !default; - - -//== Scaffolding -// -//## Settings for some of the most global styles. - -//** Background color for ``. -$body-bg: #090313 !default; -//** Global text color on ``. -$text-color: $gray-light !default; - -//** Global textual link color. -$link-color: $brand-primary !default; -//** Link hover color set via `darken()` function. -$link-hover-color: $brand-info !default; - - -//== Typography -// -//## Font, line-height, and color for body text, headings, and more. - -$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif !default; -$font-family-serif: Georgia, "Times New Roman", Times, serif !default; -//** Default monospace fonts for ``, ``, and `
`.
-$font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace !default;
-$font-family-base:        $font-family-sans-serif !default;
-
-$font-size-base:          15px !default;
-$font-size-large:         ceil(($font-size-base * 1.25)) !default; // ~18px
-$font-size-small:         ceil(($font-size-base * 0.85)) !default; // ~12px
-
-$font-size-h1:            floor(($font-size-base * 2.15)) !default;
-$font-size-h2:            floor(($font-size-base * 1.7)) !default;
-$font-size-h3:            ceil(($font-size-base * 1.25)) !default;
-$font-size-h4:            $font-size-base !default;
-$font-size-h5:            ceil(($font-size-base * 0.85)) !default;
-$font-size-h6:            ceil(($font-size-base * 0.85)) !default;
-
-//** Unit-less `line-height` for use in components like buttons.
-$line-height-base:        1.428571429 !default; // 20/14
-//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
-$line-height-computed:    floor(($font-size-base * $line-height-base)) !default; // ~20px
-
-//** By default, this inherits from the ``.
-$headings-font-family:    "Open Sans", $font-family-sans-serif !default;
-$headings-font-weight:    600 !default;
-$headings-line-height:    1.1 !default;
-$headings-color:          inherit !default;
-
-
-//== Iconography
-//
-//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
-
-//** Load fonts from this directory.
-
-// [converter] Asset helpers such as Sprockets and Node.js Mincer do not resolve relative paths
-$icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/") !default;
-
-//** File name for all font files.
-$icon-font-name:          "glyphicons-halflings-regular" !default;
-//** Element ID within SVG icon file.
-$icon-font-svg-id:        "glyphicons_halflingsregular" !default;
-
-
-//== Components
-//
-//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
-
-$padding-base-vertical:     6px !default;
-$padding-base-horizontal:   12px !default;
-
-$padding-large-vertical:    10px !default;
-$padding-large-horizontal:  16px !default;
-
-$padding-small-vertical:    5px !default;
-$padding-small-horizontal:  10px !default;
-
-$padding-xs-vertical:       1px !default;
-$padding-xs-horizontal:     5px !default;
-
-$line-height-large:         1.33 !default;
-$line-height-small:         1.5 !default;
-
-$border-radius-base:        4px !default;
-$border-radius-large:       6px !default;
-$border-radius-small:       2px !default;
-
-//** Global color for active items (e.g., navs or dropdowns).
-$component-active-color:    #fff !default;
-//** Global background color for active items (e.g., navs or dropdowns).
-$component-active-bg:       $brand-primary !default;
-
-//** Width of the `border` for generating carets that indicator dropdowns.
-$caret-width-base:          4px !default;
-//** Carets increase slightly in size for larger components.
-$caret-width-large:         5px !default;
-
-
-//== Tables
-//
-//## Customizes the `.table` component with basic values, each used across all table variations.
-
-//** Padding for ``s and ``s.
-$table-cell-padding:            8px !default;
-//** Padding for cells in `.table-condensed`.
-$table-condensed-cell-padding:  5px !default;
-
-//** Default background color used for all tables.
-$table-bg:                      transparent !default;
-//** Background color used for `.table-striped`.
-$table-bg-accent:               lighten($body-bg, 3%) !default;
-//** Background color used for `.table-hover`.
-$table-bg-hover:                $table-bg-accent !default;
-$table-bg-active:               $table-bg-hover !default;
-
-//** Border color for table and cell borders.
-$table-border-color:            lighten($body-bg, 5%) !default;
-
-
-//== Buttons
-//
-//## For each of Bootstrap's buttons, define text, background and border color.
-
-$btn-font-weight:                normal !default;
-
-$btn-default-color:              #efefef !default;
-$btn-default-bg:                 #333 !default;
-$btn-default-border:             lighten($btn-default-bg, 5%) !default;
-
-$btn-primary-color:              #fff !default;
-$btn-primary-bg:                 $brand-primary !default;
-$btn-primary-border:             darken($btn-primary-bg, 5%) !default;
-
-$btn-success-color:              #fff !default;
-$btn-success-bg:                 $brand-success !default;
-$btn-success-border:             darken($btn-success-bg, 5%) !default;
-
-$btn-info-color:                 #fff !default;
-$btn-info-bg:                    $brand-info !default;
-$btn-info-border:                darken($btn-info-bg, 5%) !default;
-
-$btn-warning-color:              #fff !default;
-$btn-warning-bg:                 $brand-warning !default;
-$btn-warning-border:             darken($btn-warning-bg, 5%) !default;
-
-$btn-danger-color:               #fff !default;
-$btn-danger-bg:                  $brand-danger !default;
-$btn-danger-border:              darken($btn-danger-bg, 5%) !default;
-
-$btn-link-disabled-color:        $gray-light !default;
-
-
-//== Forms
-//
-//##
-
-//** `` background color
-$input-bg:                       $btn-default-bg !default;
-//** `` background color
-$input-bg-disabled:              #343434 !default;
-
-//** Text color for ``s
-$input-color:                    $btn-default-color !default;
-//** `` border color
-$input-border:                   $btn-default-border !default;
-//** `` border radius
-$input-border-radius:            $border-radius-base !default;
-//** Border color for inputs on focus
-$input-border-focus:             lighten($input-bg, 15%) !default;
-
-//** Placeholder text color
-$input-color-placeholder:        lighten($input-bg, 25%) !default;
-
-//** Default `.form-control` height
-$input-height-base:              ($line-height-computed + ($padding-base-vertical * 2) + 2) !default;
-//** Large `.form-control` height
-$input-height-large:             (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default;
-//** Small `.form-control` height
-$input-height-small:             (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default;
-
-$legend-color:                   $gray-dark !default;
-$legend-border-color:            #e5e5e5 !default;
-
-//** Background color for textual input addons
-$input-group-addon-bg:           $gray-lighter !default;
-//** Border color for textual input addons
-$input-group-addon-border-color: $input-border !default;
-
-
-//== Dropdowns
-//
-//## Dropdown menu container and contents.
-
-//** Background for the dropdown menu.
-$dropdown-bg:                    #fff !default;
-//** Dropdown menu `border-color`.
-$dropdown-border:                rgba(0,0,0,.15) !default;
-//** Dropdown menu `border-color` **for IE8**.
-$dropdown-fallback-border:       #ccc !default;
-//** Divider color for between dropdown items.
-$dropdown-divider-bg:            #e5e5e5 !default;
-
-//** Dropdown link text color.
-$dropdown-link-color:            $gray-dark !default;
-//** Hover color for dropdown links.
-$dropdown-link-hover-color:      darken($gray-dark, 5%) !default;
-//** Hover background for dropdown links.
-$dropdown-link-hover-bg:         #f5f5f5 !default;
-
-//** Active dropdown menu item text color.
-$dropdown-link-active-color:     $component-active-color !default;
-//** Active dropdown menu item background color.
-$dropdown-link-active-bg:        $component-active-bg !default;
-
-//** Disabled dropdown menu item background color.
-$dropdown-link-disabled-color:   $gray-light !default;
-
-//** Text color for headers within dropdown menus.
-$dropdown-header-color:          $gray-light !default;
-
-//** Deprecated `$dropdown-caret-color` as of v3.1.0
-$dropdown-caret-color:           #000 !default;
-
-
-//-- Z-index master list
-//
-// Warning: Avoid customizing these values. They're used for a bird's eye view
-// of components dependent on the z-axis and are designed to all work together.
-//
-// Note: These variables are not generated into the Customizer.
-
-$zindex-navbar:            1000 !default;
-$zindex-dropdown:          1000 !default;
-$zindex-popover:           1060 !default;
-$zindex-tooltip:           1070 !default;
-$zindex-navbar-fixed:      1030 !default;
-$zindex-modal-background:  1040 !default;
-$zindex-modal:             1050 !default;
-
-
-//== Media queries breakpoints
-//
-//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
-
-// Extra small screen / phone
-//** Deprecated `$screen-xs` as of v3.0.1
-$screen-xs:                  480px !default;
-//** Deprecated `$screen-xs-min` as of v3.2.0
-$screen-xs-min:              $screen-xs !default;
-//** Deprecated `$screen-phone` as of v3.0.1
-$screen-phone:               $screen-xs-min !default;
-
-// Small screen / tablet
-//** Deprecated `$screen-sm` as of v3.0.1
-$screen-sm:                  768px !default;
-$screen-sm-min:              $screen-sm !default;
-//** Deprecated `$screen-tablet` as of v3.0.1
-$screen-tablet:              $screen-sm-min !default;
-
-// Medium screen / desktop
-//** Deprecated `$screen-md` as of v3.0.1
-$screen-md:                  992px !default;
-$screen-md-min:              $screen-md !default;
-//** Deprecated `$screen-desktop` as of v3.0.1
-$screen-desktop:             $screen-md-min !default;
-
-// Large screen / wide desktop
-//** Deprecated `$screen-lg` as of v3.0.1
-$screen-lg:                  1200px !default;
-$screen-lg-min:              $screen-lg !default;
-//** Deprecated `$screen-lg-desktop` as of v3.0.1
-$screen-lg-desktop:          $screen-lg-min !default;
-
-// So media queries don't overlap when required, provide a maximum
-$screen-xs-max:              ($screen-sm-min - 1) !default;
-$screen-sm-max:              ($screen-md-min - 1) !default;
-$screen-md-max:              ($screen-lg-min - 1) !default;
-
-
-//== Grid system
-//
-//## Define your custom responsive grid.
-
-//** Number of columns in the grid.
-$grid-columns:              12 !default;
-//** Padding between columns. Gets divided in half for the left and right.
-$grid-gutter-width:         30px !default;
-// Navbar collapse
-//** Point at which the navbar becomes uncollapsed.
-$grid-float-breakpoint:     $screen-sm-min !default;
-//** Point at which the navbar begins collapsing.
-$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
-
-
-//== Container sizes
-//
-//## Define the maximum width of `.container` for different screen sizes.
-
-// Small screen / tablet
-$container-tablet:             ((720px + $grid-gutter-width)) !default;
-//** For `$screen-sm-min` and up.
-$container-sm:                 $container-tablet !default;
-
-// Medium screen / desktop
-$container-desktop:            ((940px + $grid-gutter-width)) !default;
-//** For `$screen-md-min` and up.
-$container-md:                 $container-desktop !default;
-
-// Large screen / wide desktop
-$container-large-desktop:      ((1140px + $grid-gutter-width)) !default;
-//** For `$screen-lg-min` and up.
-$container-lg:                 $container-large-desktop !default;
-
-
-//== Navbar
-//
-//##
-
-// Basics of a navbar
-$navbar-height:                    50px !default;
-$navbar-margin-bottom:             $line-height-computed !default;
-$navbar-border-radius:             $border-radius-base !default;
-$navbar-padding-horizontal:        floor(($grid-gutter-width / 2)) !default;
-$navbar-padding-vertical:          (($navbar-height - $line-height-computed) / 2) !default;
-$navbar-collapse-max-height:       340px !default;
-
-$navbar-default-color:             #777 !default;
-$navbar-default-bg:                #f8f8f8 !default;
-$navbar-default-border:            darken($navbar-default-bg, 6.5%) !default;
-
-// Navbar links
-$navbar-default-link-color:                #777 !default;
-$navbar-default-link-hover-color:          #333 !default;
-$navbar-default-link-hover-bg:             transparent !default;
-$navbar-default-link-active-color:         #555 !default;
-$navbar-default-link-active-bg:            darken($navbar-default-bg, 6.5%) !default;
-$navbar-default-link-disabled-color:       #ccc !default;
-$navbar-default-link-disabled-bg:          transparent !default;
-
-// Navbar brand label
-$navbar-default-brand-color:               $navbar-default-link-color !default;
-$navbar-default-brand-hover-color:         darken($navbar-default-brand-color, 10%) !default;
-$navbar-default-brand-hover-bg:            transparent !default;
-
-// Navbar toggle
-$navbar-default-toggle-hover-bg:           #ddd !default;
-$navbar-default-toggle-icon-bar-bg:        #888 !default;
-$navbar-default-toggle-border-color:       #ddd !default;
-
-
-// Inverted navbar
-// Reset inverted navbar basics
-$navbar-inverse-color:                      $gray-light !default;
-$navbar-inverse-bg:                         $body-bg !default;
-$navbar-inverse-border:                     darken($navbar-inverse-bg, 10%) !default;
-
-// Inverted navbar links
-$navbar-inverse-link-color:                 $gray-light !default;
-$navbar-inverse-link-hover-color:           #fff !default;
-$navbar-inverse-link-hover-bg:              transparent !default;
-$navbar-inverse-link-active-color:          $navbar-inverse-link-hover-color !default;
-$navbar-inverse-link-active-bg:             darken($navbar-inverse-bg, 10%) !default;
-$navbar-inverse-link-disabled-color:        #444 !default;
-$navbar-inverse-link-disabled-bg:           transparent !default;
-
-// Inverted navbar brand label
-$navbar-inverse-brand-color:                $navbar-inverse-link-color !default;
-$navbar-inverse-brand-hover-color:          #fff !default;
-$navbar-inverse-brand-hover-bg:             transparent !default;
-
-// Inverted navbar toggle
-$navbar-inverse-toggle-hover-bg:            #333 !default;
-$navbar-inverse-toggle-icon-bar-bg:         #fff !default;
-$navbar-inverse-toggle-border-color:        #333 !default;
-
-
-//== Navs
-//
-//##
-
-//=== Shared nav styles
-$nav-link-padding:                          10px 15px !default;
-$nav-link-hover-bg:                         $gray-lighter !default;
-
-$nav-disabled-link-color:                   $gray-light !default;
-$nav-disabled-link-hover-color:             $gray-light !default;
-
-$nav-open-link-hover-color:                 #fff !default;
-
-//== Tabs
-$nav-tabs-border-color:                     #ddd !default;
-
-$nav-tabs-link-hover-border-color:          $gray-lighter !default;
-
-$nav-tabs-active-link-hover-bg:             $body-bg !default;
-$nav-tabs-active-link-hover-color:          $gray !default;
-$nav-tabs-active-link-hover-border-color:   #ddd !default;
-
-$nav-tabs-justified-link-border-color:            #ddd !default;
-$nav-tabs-justified-active-link-border-color:     $body-bg !default;
-
-//== Pills
-$nav-pills-border-radius:                   $border-radius-base !default;
-$nav-pills-active-link-hover-bg:            $component-active-bg !default;
-$nav-pills-active-link-hover-color:         $component-active-color !default;
-
-
-//== Pagination
-//
-//##
-
-$pagination-color:                     $link-color !default;
-$pagination-bg:                        lighten($body-bg, 5%) !default;
-$pagination-border:                    lighten($body-bg, 7%) !default;
-
-$pagination-hover-color:               $link-color !default;
-$pagination-hover-bg:                  lighten($body-bg, 10%) !default;
-$pagination-hover-border:              lighten($body-bg, 12%) !default;
-
-$pagination-active-color:              #fff !default;
-$pagination-active-bg:                 $brand-primary !default;
-$pagination-active-border:             $brand-primary !default;
-
-$pagination-disabled-color:            $gray-light !default;
-$pagination-disabled-bg:               lighten($body-bg, 10%) !default;
-$pagination-disabled-border:           lighten($body-bg, 15%) !default;
-
-
-//== Pager
-//
-//##
-
-$pager-bg:                             $pagination-bg !default;
-$pager-border:                         $pagination-border !default;
-$pager-border-radius:                  15px !default;
-
-$pager-hover-bg:                       $pagination-hover-bg !default;
-
-$pager-active-bg:                      $pagination-active-bg !default;
-$pager-active-color:                   $pagination-active-color !default;
-
-$pager-disabled-color:                 $pagination-disabled-color !default;
-
-
-//== Jumbotron
-//
-//##
-
-$jumbotron-padding:              30px !default;
-$jumbotron-color:                inherit !default;
-$jumbotron-bg:                   lighten($body-bg, 10%) !default;
-$jumbotron-heading-color:        inherit !default;
-$jumbotron-font-size:            ceil(($font-size-base * 1.5)) !default;
-
-
-//== Form states and alerts
-//
-//## Define colors for form feedback states and, by default, alerts.
-
-$state-success-text:             #fff !default;
-$state-success-bg:               #6EAB34 !default;
-$state-success-border:           darken(adjust-hue($state-success-bg, -10), 5%) !default;
-
-$state-info-text:                #fff !default;
-$state-info-bg:                  #439CAE !default;
-$state-info-border:              darken(adjust-hue($state-info-bg, -10), 7%) !default;
-
-$state-warning-text:             #fff !default;
-$state-warning-bg:               #AF6D19 !default;
-$state-warning-border:           darken(adjust-hue($state-warning-bg, -10), 5%) !default;
-
-$state-danger-text:              #fff !default;
-$state-danger-bg:                #AF3519 !default;
-$state-danger-border:            darken(adjust-hue($state-danger-bg, -10), 5%) !default;
-
-
-//== Tooltips
-//
-//##
-
-//** Tooltip max width
-$tooltip-max-width:           200px !default;
-//** Tooltip text color
-$tooltip-color:               #fff !default;
-//** Tooltip background color
-$tooltip-bg:                  darken($brand-primary, 10%) !default;
-$tooltip-opacity:             0.9 !default;
-
-//** Tooltip arrow width
-$tooltip-arrow-width:         5px !default;
-//** Tooltip arrow color
-$tooltip-arrow-color:         $tooltip-bg !default;
-
-
-//== Popovers
-//
-//##
-
-//** Popover body background color
-$popover-bg:                          #fff !default;
-//** Popover maximum width
-$popover-max-width:                   276px !default;
-//** Popover border color
-$popover-border-color:                rgba(0,0,0,.2) !default;
-//** Popover fallback border color
-$popover-fallback-border-color:       #ccc !default;
-
-//** Popover title background color
-$popover-title-bg:                    darken($popover-bg, 3%) !default;
-
-//** Popover arrow width
-$popover-arrow-width:                 10px !default;
-//** Popover arrow color
-$popover-arrow-color:                 #fff !default;
-
-//** Popover outer arrow width
-$popover-arrow-outer-width:           ($popover-arrow-width + 1) !default;
-//** Popover outer arrow color
-$popover-arrow-outer-color:           fade_in($popover-border-color, 0.05) !default;
-//** Popover outer arrow fallback color
-$popover-arrow-outer-fallback-color:  darken($popover-fallback-border-color, 20%) !default;
-
-
-//== Labels
-//
-//##
-
-//** Default label background color
-$label-default-bg:            $gray-light !default;
-//** Primary label background color
-$label-primary-bg:            $brand-primary !default;
-//** Success label background color
-$label-success-bg:            $brand-success !default;
-//** Info label background color
-$label-info-bg:               $brand-info !default;
-//** Warning label background color
-$label-warning-bg:            $brand-warning !default;
-//** Danger label background color
-$label-danger-bg:             $brand-danger !default;
-
-//** Default label text color
-$label-color:                 #fff !default;
-//** Default text color of a linked label
-$label-link-hover-color:      #fff !default;
-
-
-//== Modals
-//
-//##
-
-//** Padding applied to the modal body
-$modal-inner-padding:         15px !default;
-
-//** Padding applied to the modal title
-$modal-title-padding:         15px !default;
-//** Modal title line-height
-$modal-title-line-height:     $line-height-base !default;
-
-//** Background color of modal content area
-$modal-content-bg:                             #fff !default;
-//** Modal content border color
-$modal-content-border-color:                   rgba(0,0,0,.2) !default;
-//** Modal content border color **for IE8**
-$modal-content-fallback-border-color:          #999 !default;
-
-//** Modal backdrop background color
-$modal-backdrop-bg:           #000 !default;
-//** Modal backdrop opacity
-$modal-backdrop-opacity:      .5 !default;
-//** Modal header border color
-$modal-header-border-color:   #e5e5e5 !default;
-//** Modal footer border color
-$modal-footer-border-color:   $modal-header-border-color !default;
-
-$modal-lg:                    900px !default;
-$modal-md:                    600px !default;
-$modal-sm:                    300px !default;
-
-
-//== Alerts
-//
-//## Define alert colors, border radius, and padding.
-
-$alert-padding:               15px !default;
-$alert-border-radius:         $border-radius-base !default;
-$alert-link-font-weight:      bold !default;
-
-$alert-success-bg:            $state-success-bg !default;
-$alert-success-text:          $state-success-text !default;
-$alert-success-border:        $state-success-border !default;
-
-$alert-info-bg:               $state-info-bg !default;
-$alert-info-text:             $state-info-text !default;
-$alert-info-border:           $state-info-border !default;
-
-$alert-warning-bg:            $state-warning-bg !default;
-$alert-warning-text:          $state-warning-text !default;
-$alert-warning-border:        $state-warning-border !default;
-
-$alert-danger-bg:             $state-danger-bg !default;
-$alert-danger-text:           $state-danger-text !default;
-$alert-danger-border:         $state-danger-border !default;
-
-
-//== Progress bars
-//
-//##
-
-//** Background color of the whole progress component
-$progress-bg:                 #f5f5f5 !default;
-//** Progress bar text color
-$progress-bar-color:          #fff !default;
-
-//** Default progress bar color
-$progress-bar-bg:             $brand-primary !default;
-//** Success progress bar color
-$progress-bar-success-bg:     $brand-success !default;
-//** Warning progress bar color
-$progress-bar-warning-bg:     $brand-warning !default;
-//** Danger progress bar color
-$progress-bar-danger-bg:      $brand-danger !default;
-//** Info progress bar color
-$progress-bar-info-bg:        $brand-info !default;
-
-
-//== List group
-//
-//##
-
-//** Background color on `.list-group-item`
-$list-group-bg:                 #fff !default;
-//** `.list-group-item` border color
-$list-group-border:             #ddd !default;
-//** List group border radius
-$list-group-border-radius:      $border-radius-base !default;
-
-//** Background color of single list items on hover
-$list-group-hover-bg:           #f5f5f5 !default;
-//** Text color of active list items
-$list-group-active-color:       $component-active-color !default;
-//** Background color of active list items
-$list-group-active-bg:          $component-active-bg !default;
-//** Border color of active list elements
-$list-group-active-border:      $list-group-active-bg !default;
-//** Text color for content within active list items
-$list-group-active-text-color:  lighten($list-group-active-bg, 40%) !default;
-
-//** Text color of disabled list items
-$list-group-disabled-color:      $gray-light !default;
-//** Background color of disabled list items
-$list-group-disabled-bg:         $gray-lighter !default;
-//** Text color for content within disabled list items
-$list-group-disabled-text-color: $list-group-disabled-color !default;
-
-$list-group-link-color:         #555 !default;
-$list-group-link-hover-color:   $list-group-link-color !default;
-$list-group-link-heading-color: #333 !default;
-
-
-//== Panels
-//
-//##
-
-$panel-bg:                    #fff !default;
-$panel-body-padding:          15px !default;
-$panel-heading-padding:       10px 15px !default;
-$panel-footer-padding:        $panel-heading-padding !default;
-$panel-border-radius:         $border-radius-base !default;
-
-//** Border color for elements within panels
-$panel-inner-border:          #ddd !default;
-$panel-footer-bg:             #f5f5f5 !default;
-
-$panel-default-text:          $gray-dark !default;
-$panel-default-border:        #ddd !default;
-$panel-default-heading-bg:    #f5f5f5 !default;
-
-$panel-primary-text:          #fff !default;
-$panel-primary-border:        $brand-primary !default;
-$panel-primary-heading-bg:    $brand-primary !default;
-
-$panel-success-text:          $state-success-text !default;
-$panel-success-border:        $state-success-border !default;
-$panel-success-heading-bg:    $state-success-bg !default;
-
-$panel-info-text:             $state-info-text !default;
-$panel-info-border:           $state-info-border !default;
-$panel-info-heading-bg:       $state-info-bg !default;
-
-$panel-warning-text:          $state-warning-text !default;
-$panel-warning-border:        $state-warning-border !default;
-$panel-warning-heading-bg:    $state-warning-bg !default;
-
-$panel-danger-text:           $state-danger-text !default;
-$panel-danger-border:         $state-danger-border !default;
-$panel-danger-heading-bg:     $state-danger-bg !default;
-
-
-//== Thumbnails
-//
-//##
-
-//** Padding around the thumbnail image
-$thumbnail-padding:           4px !default;
-//** Thumbnail background color
-$thumbnail-bg:                $body-bg !default;
-//** Thumbnail border color
-$thumbnail-border:            #ddd !default;
-//** Thumbnail border radius
-$thumbnail-border-radius:     $border-radius-base !default;
-
-//** Custom text color for thumbnail captions
-$thumbnail-caption-color:     $text-color !default;
-//** Padding around the thumbnail caption
-$thumbnail-caption-padding:   9px !default;
-
-
-//== Wells
-//
-//##
-
-$well-bg:                     #f5f5f5 !default;
-$well-border:                 darken($well-bg, 7%) !default;
-
-
-//== Badges
-//
-//##
-
-$badge-color:                 #fff !default;
-//** Linked badge text color on hover
-$badge-link-hover-color:      #fff !default;
-$badge-bg:                    $gray-light !default;
-
-//** Badge text color in active nav link
-$badge-active-color:          $link-color !default;
-//** Badge background color in active nav link
-$badge-active-bg:             #fff !default;
-
-$badge-font-weight:           bold !default;
-$badge-line-height:           1 !default;
-$badge-border-radius:         10px !default;
-
-
-//== Breadcrumbs
-//
-//##
-
-$breadcrumb-padding-vertical:   8px !default;
-$breadcrumb-padding-horizontal: 15px !default;
-//** Breadcrumb background color
-$breadcrumb-bg:                 #f5f5f5 !default;
-//** Breadcrumb text color
-$breadcrumb-color:              #ccc !default;
-//** Text color of current page in the breadcrumb
-$breadcrumb-active-color:       $gray-light !default;
-//** Textual separator for between breadcrumb elements
-$breadcrumb-separator:          "/" !default;
-
-
-//== Carousel
-//
-//##
-
-$carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6) !default;
-
-$carousel-control-color:                      #fff !default;
-$carousel-control-width:                      15% !default;
-$carousel-control-opacity:                    .5 !default;
-$carousel-control-font-size:                  20px !default;
-
-$carousel-indicator-active-bg:                #fff !default;
-$carousel-indicator-border-color:             #fff !default;
-
-$carousel-caption-color:                      #fff !default;
-
-
-//== Close
-//
-//##
-
-$close-font-weight:           bold !default;
-$close-color:                 #000 !default;
-$close-text-shadow:           0 1px 0 #fff !default;
-
-
-//== Code
-//
-//##
-
-$code-color:                  #c7254e !default;
-$code-bg:                     #f9f2f4 !default;
-
-$kbd-color:                   #fff !default;
-$kbd-bg:                      #333 !default;
-
-$pre-bg:                      #f5f5f5 !default;
-$pre-color:                   $gray-dark !default;
-$pre-border-color:            #ccc !default;
-$pre-scrollable-max-height:   340px !default;
-
-
-//== Type
-//
-//##
-
-//** Horizontal offset for forms and lists.
-$component-offset-horizontal: 180px !default;
-//** Text muted color
-$text-muted:                  $gray-lighter !default;
-//** Abbreviations and acronyms border color
-$abbr-border-color:           $gray-dark !default;
-//** Headings small color
-$headings-small-color:        $gray-lighter !default;
-//** Blockquote small color
-$blockquote-small-color:      $gray-lighter !default;
-//** Blockquote font size
-$blockquote-font-size:        ($font-size-base * 1.25) !default;
-//** Blockquote border color
-$blockquote-border-color:     lighten($body-bg, 15%) !default;
-//** Page header border color
-$page-header-border-color:    lighten($body-bg, 15%) !default;
-//** Width of horizontal description list titles
-$dl-horizontal-offset:        $component-offset-horizontal !default;
-//** Horizontal line color.
-$hr-border:                   lighten($body-bg, 15%) !default;
-
-
diff --git a/client/app/styles/bootstrap.scss b/client/app/styles/bootstrap.scss
deleted file mode 100644
index 2f44e7a..0000000
--- a/client/app/styles/bootstrap.scss
+++ /dev/null
@@ -1,50 +0,0 @@
-// Core variables and mixins
-@import "variables";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/mixins";
-
-// Reset and dependencies
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/normalize";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/print";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/glyphicons";
-
-// Core CSS
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/scaffolding";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/type";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/code";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/grid";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/tables";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/forms";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/buttons";
-
-// Components
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/component-animations";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/dropdowns";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/button-groups";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/input-groups";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/navs";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/navbar";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/breadcrumbs";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/pagination";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/pager";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/labels";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/badges";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/jumbotron";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/thumbnails";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/alerts";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/progress-bars";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/media";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/list-group";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/panels";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/responsive-embed";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/wells";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/close";
-
-// Components w/ JavaScript
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/modals";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/tooltip";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/popovers";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/carousel";
-
-// Utility classes
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/utilities";
-@import "bootstrap-sass-official/assets/stylesheets/bootstrap/responsive-utilities";
diff --git a/client/bower.json b/client/bower.json
deleted file mode 100644
index ab6b03c..0000000
--- a/client/bower.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
-  "name": "blicblock",
-  "version": "0.0.0",
-  "dependencies": {
-    "angular": "1.2.16",
-    "json3": "~3.3.1",
-    "es5-shim": "~3.1.0",
-    "bootstrap-sass-official": "~3.2.0",
-    "angular-resource": "1.2.16",
-    "angular-cookies": "1.2.16",
-    "angular-sanitize": "1.2.16",
-    "angular-animate": "1.2.16",
-    "angular-touch": "1.2.16",
-    "angular-route": "1.2.16",
-    "animate-css": "~3.2.0",
-    "angular-bootstrap": "~0.11.0",
-    "angular-local-storage": "~0.0.7",
-    "angular-moment": "~0.8.2",
-    "angular-swipe": "~0.0.6",
-    "angularjs-multiselect": "~2.0.2"
-  },
-  "devDependencies": {
-    "angular-mocks": "1.2.16",
-    "angular-scenario": "1.2.16",
-    "jquery": "~2.1.1"
-  },
-  "appPath": "app",
-  "resolutions": {
-    "angular": "1.2.16"
-  }
-}
diff --git a/client/package.json b/client/package.json
deleted file mode 100644
index 601221a..0000000
--- a/client/package.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
-  "name": "blicblockJS",
-  "description": "A JavaScript implementation of the game Blicblock that your Sims play in The Sims 4.",
-  "repository": {
-    "type": "git",
-    "url": "git://github.com/cheshire137/blicblock-js.git"
-  },
-  "version": "0.0.0",
-  "dependencies": {},
-  "devDependencies": {
-    "coffee-script": "^1.8.0",
-    "grunt": "^0.4.1",
-    "grunt-autoprefixer": "^0.7.3",
-    "grunt-compass-multiple": "^0.2.1",
-    "grunt-concurrent": "^0.5.0",
-    "grunt-connect-proxy": "^0.1.10",
-    "grunt-contrib-clean": "^0.5.0",
-    "grunt-contrib-coffee": "^0.10.1",
-    "grunt-contrib-compass": "^0.7.2",
-    "grunt-contrib-concat": "^0.4.0",
-    "grunt-contrib-connect": "^0.7.1",
-    "grunt-contrib-copy": "^0.5.0",
-    "grunt-contrib-cssmin": "^0.9.0",
-    "grunt-contrib-htmlmin": "^0.3.0",
-    "grunt-contrib-imagemin": "^0.7.0",
-    "grunt-contrib-jshint": "^0.10.0",
-    "grunt-contrib-uglify": "^0.4.0",
-    "grunt-contrib-watch": "^0.6.1",
-    "grunt-filerev": "^0.2.1",
-    "grunt-google-cdn": "^0.4.0",
-    "grunt-karma": "^0.9.0",
-    "grunt-newer": "^0.7.0",
-    "grunt-ngmin": "^0.0.3",
-    "grunt-rails-server": "^0.1.0",
-    "grunt-shell-spawn": "^0.3.0",
-    "grunt-svgmin": "^0.4.0",
-    "grunt-usemin": "^2.1.1",
-    "grunt-wiredep": "^1.9.0",
-    "jshint-stylish": "^0.2.0",
-    "karma": "^0.12.23",
-    "karma-coffee-preprocessor": "^0.2.1",
-    "karma-jasmine": "^0.1.5",
-    "karma-phantomjs-launcher": "^0.1.4",
-    "load-grunt-tasks": "^0.4.0",
-    "time-grunt": "^0.3.1"
-  },
-  "engines": {
-    "node": ">=0.10.0"
-  },
-  "scripts": {
-    "preinstall": "npm install -g bower grunt-cli",
-    "postinstall": "bower install",
-    "test": "grunt test --single-run"
-  }
-}
diff --git a/config/application.rb b/config/application.rb
index 10fc69c..b0235e7 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -30,6 +30,10 @@ class Application < Rails::Application
         controller_specs: true
     end
 
+    config.react.addons = true
+
     config.gem 'geokit'
+
+    config.sass.preferred_syntax = :scss
   end
 end
diff --git a/config/database.yml b/config/database.yml
index f4e7abe..f1b30fb 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -24,8 +24,6 @@ default: &default
 development:
   <<: *default
   database: blicblockjs_development
-  username: blicblockjs
-  password: password
 
   # The specified database role being used to connect to postgres.
   # To create additional roles in postgres see `$ createuser --help`.
@@ -60,8 +58,6 @@ development:
 test:
   <<: *default
   database: blicblockjs_test
-  username: blicblockjs
-  password: password
 
 # As with config/secrets.yml, you never want to store sensitive information,
 # like your database password, in your source code. If your source code is
diff --git a/config/routes.rb b/config/routes.rb
index dff97de..1d53f3a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,5 @@
 Rails.application.routes.draw do
+  mount JasmineRails::Engine => '/specs' if defined?(JasmineRails)
   scope '/api' do
     resources :scores, defaults: {format: :json},
                        only: [:index, :show, :create] do
@@ -7,5 +8,5 @@
       end
     end
   end
-  root to: redirect('/api/scores.json')
+  root to: 'home#index'
 end
diff --git a/db/schema.rb b/db/schema.rb
index 955768d..5f1a26f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -17,16 +17,16 @@
   enable_extension "plpgsql"
 
   create_table "locations", force: :cascade do |t|
-    t.string   "country"
+    t.string   "country",      limit: 255
     t.string   "country_code", limit: 3
     t.datetime "created_at"
     t.datetime "updated_at"
   end
 
   create_table "scores", force: :cascade do |t|
-    t.string   "initials"
-    t.integer  "value",       null: false
-    t.string   "ip_address"
+    t.string   "initials",    limit: 255
+    t.integer  "value",                   null: false
+    t.string   "ip_address",  limit: 255
     t.datetime "created_at"
     t.datetime "updated_at"
     t.integer  "location_id"
diff --git a/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.eot b/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.eot
deleted file mode 100644
index 4a4ca86..0000000
Binary files a/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.eot and /dev/null differ
diff --git a/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.svg b/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.svg
deleted file mode 100644
index e3e2dc7..0000000
--- a/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.svg
+++ /dev/null
@@ -1,229 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 
\ No newline at end of file
diff --git a/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf b/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf
deleted file mode 100644
index 67fa00b..0000000
Binary files a/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf and /dev/null differ
diff --git a/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.woff b/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.woff
deleted file mode 100644
index 8c54182..0000000
Binary files a/public/bower_components/bootstrap-sass-official/assets/fonts/bootstrap/glyphicons-halflings-regular.woff and /dev/null differ
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index defae33..0000000
--- a/public/index.html
+++ /dev/null
@@ -1,10 +0,0 @@
-    BlicblockJS                      

{{error.message}}

{{notice.message}}

\ No newline at end of file diff --git a/public/scripts/oldieshim.ff90b0fb.js b/public/scripts/oldieshim.ff90b0fb.js deleted file mode 100644 index 3c47282..0000000 --- a/public/scripts/oldieshim.ff90b0fb.js +++ /dev/null @@ -1 +0,0 @@ -!function(a,b){"function"==typeof define&&define.amd?define(b):"object"==typeof exports?module.exports=b():a.returnExports=b()}(this,function(){function a(){}function b(a){return a=+a,a!==a?a=0:0!==a&&a!==1/0&&a!==-(1/0)&&(a=(a>0||-1)*Math.floor(Math.abs(a))),a}function c(a){var b=typeof a;return null===a||"undefined"===b||"boolean"===b||"number"===b||"string"===b}function d(a){var b,d,e;if(c(a))return a;if(d=a.valueOf,l(d)&&(b=d.call(a),c(b)))return b;if(e=a.toString,l(e)&&(b=e.call(a),c(b)))return b;throw new TypeError}var e=Function.prototype.call,f=Array.prototype,g=Object.prototype,h=f.slice,i=Array.prototype.splice,j=Array.prototype.push,k=Array.prototype.unshift,l=function(a){return"[object Function]"===g.toString.call(a)},m=function(a){return"[object RegExp]"===g.toString.call(a)};Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(!l(c))throw new TypeError("Function.prototype.bind called on incompatible "+c);for(var d=h.call(arguments,1),e=function(){if(this instanceof j){var a=c.apply(this,d.concat(h.call(arguments)));return Object(a)===a?a:this}return c.apply(b,d.concat(h.call(arguments)))},f=Math.max(0,c.length-d.length),g=[],i=0;f>i;i++)g.push("$"+i);var j=Function("binder","return function("+g.join(",")+"){return binder.apply(this,arguments)}")(e);return c.prototype&&(a.prototype=c.prototype,j.prototype=new a,a.prototype=null),j});var n,o,p,q,r,s=e.bind(g.hasOwnProperty),t=e.bind(g.toString);(r=s(g,"__defineGetter__"))&&(n=e.bind(g.__defineGetter__),o=e.bind(g.__defineSetter__),p=e.bind(g.__lookupGetter__),q=e.bind(g.__lookupSetter__)),2!==[1,2].splice(0).length&&(Array.prototype.splice=function(){function a(a){for(var b=[];a--;)b.unshift(a);return b}var b,c=[];return c.splice.bind(c,0,0).apply(null,a(20)),c.splice.bind(c,0,0).apply(null,a(26)),b=c.length,c.splice(5,0,"XXX"),b+1===c.length?!0:void 0}()?function(a,b){return arguments.length?i.apply(this,[void 0===a?0:a,void 0===b?this.length-a:b].concat(h.call(arguments,2))):[]}:function(a,b){var c,d=h.call(arguments,2),e=d.length;if(!arguments.length)return[];if(void 0===a&&(a=0),void 0===b&&(b=this.length-a),e>0){if(0>=b){if(a===this.length)return j.apply(this,d),[];if(0===a)return k.apply(this,d),[]}return c=h.call(this,a,a+b),d.push.apply(d,h.call(this,a+b,this.length)),d.unshift.apply(d,h.call(this,0,a)),d.unshift(0,this.length),i.apply(this,d),c}return i.call(this,a,b)}),1!==[].unshift(0)&&(Array.prototype.unshift=function(){return k.apply(this,arguments),this.length}),Array.isArray||(Array.isArray=function(a){return"[object Array]"===t(a)});var u=Object("a"),v="a"!==u[0]||!(0 in u),w=function(a){var b=!0;return a&&a.call("foo",function(a,c,d){"object"!=typeof d&&(b=!1)}),!!a&&b};Array.prototype.forEach&&w(Array.prototype.forEach)||(Array.prototype.forEach=function(a){var b=S(this),c=v&&"[object String]"===t(this)?this.split(""):b,d=arguments[1],e=-1,f=c.length>>>0;if(!l(a))throw new TypeError;for(;++e>>0,e=Array(d),f=arguments[1];if(!l(a))throw new TypeError(a+" is not a function");for(var g=0;d>g;g++)g in c&&(e[g]=a.call(f,c[g],g,b));return e}),Array.prototype.filter&&w(Array.prototype.filter)||(Array.prototype.filter=function(a){var b,c=S(this),d=v&&"[object String]"===t(this)?this.split(""):c,e=d.length>>>0,f=[],g=arguments[1];if(!l(a))throw new TypeError(a+" is not a function");for(var h=0;e>h;h++)h in d&&(b=d[h],a.call(g,b,h,c)&&f.push(b));return f}),Array.prototype.every&&w(Array.prototype.every)||(Array.prototype.every=function(a){var b=S(this),c=v&&"[object String]"===t(this)?this.split(""):b,d=c.length>>>0,e=arguments[1];if(!l(a))throw new TypeError(a+" is not a function");for(var f=0;d>f;f++)if(f in c&&!a.call(e,c[f],f,b))return!1;return!0}),Array.prototype.some&&w(Array.prototype.some)||(Array.prototype.some=function(a){var b=S(this),c=v&&"[object String]"===t(this)?this.split(""):b,d=c.length>>>0,e=arguments[1];if(!l(a))throw new TypeError(a+" is not a function");for(var f=0;d>f;f++)if(f in c&&a.call(e,c[f],f,b))return!0;return!1});var x=!1;if(Array.prototype.reduce&&(x="object"==typeof Array.prototype.reduce.call("a",function(a,b,c,d){return d})),Array.prototype.reduce&&x||(Array.prototype.reduce=function(a){var b=S(this),c=v&&"[object String]"===t(this)?this.split(""):b,d=c.length>>>0;if(!l(a))throw new TypeError(a+" is not a function");if(!d&&1===arguments.length)throw new TypeError("reduce of empty array with no initial value");var e,f=0;if(arguments.length>=2)e=arguments[1];else for(;;){if(f in c){e=c[f++];break}if(++f>=d)throw new TypeError("reduce of empty array with no initial value")}for(;d>f;f++)f in c&&(e=a.call(void 0,e,c[f],f,b));return e}),Array.prototype.reduceRight||(Array.prototype.reduceRight=function(a){var b=S(this),c=v&&"[object String]"===t(this)?this.split(""):b,d=c.length>>>0;if(!l(a))throw new TypeError(a+" is not a function");if(!d&&1===arguments.length)throw new TypeError("reduceRight of empty array with no initial value");var e,f=d-1;if(arguments.length>=2)e=arguments[1];else for(;;){if(f in c){e=c[f--];break}if(--f<0)throw new TypeError("reduceRight of empty array with no initial value")}if(0>f)return e;do f in this&&(e=a.call(void 0,e,c[f],f,b));while(f--);return e}),Array.prototype.indexOf&&-1===[0,1].indexOf(1,2)||(Array.prototype.indexOf=function(a){var c=v&&"[object String]"===t(this)?this.split(""):S(this),d=c.length>>>0;if(!d)return-1;var e=0;for(arguments.length>1&&(e=b(arguments[1])),e=e>=0?e:Math.max(0,d+e);d>e;e++)if(e in c&&c[e]===a)return e;return-1}),Array.prototype.lastIndexOf&&-1===[0,1].lastIndexOf(0,-3)||(Array.prototype.lastIndexOf=function(a){var c=v&&"[object String]"===t(this)?this.split(""):S(this),d=c.length>>>0;if(!d)return-1;var e=d-1;for(arguments.length>1&&(e=Math.min(e,b(arguments[1]))),e=e>=0?e:d-Math.abs(e);e>=0;e--)if(e in c&&a===c[e])return e;return-1}),!Object.keys){var y=!{toString:null}.propertyIsEnumerable("toString"),z=function(){}.propertyIsEnumerable("prototype"),A=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],B=A.length,C=function(a){var b=t(a),c="[object Arguments]"===b;return c||(c=!Array.isArray(b)&&null!==a&&"object"==typeof a&&"number"==typeof a.length&&a.length>=0&&l(a.callee)),c};Object.keys=function(a){var b=l(a),c=C(a),d=null!==a&&"object"==typeof a,e=d&&"[object String]"===t(a);if(!d&&!b&&!c)throw new TypeError("Object.keys called on a non-object");var f=[],g=z&&b;if(e||c)for(var h=0;hm;m++){var n=A[m];k&&"constructor"===n||!s(a,n)||f.push(n)}return f}}var D=-621987552e5,E="-000001";Date.prototype.toISOString&&-1!==new Date(D).toISOString().indexOf(E)||(Date.prototype.toISOString=function(){var a,b,c,d,e;if(!isFinite(this))throw new RangeError("Date.prototype.toISOString called on non-finite value.");for(d=this.getUTCFullYear(),e=this.getUTCMonth(),d+=Math.floor(e/12),e=(e%12+12)%12,a=[e+1,this.getUTCDate(),this.getUTCHours(),this.getUTCMinutes(),this.getUTCSeconds()],d=(0>d?"-":d>9999?"+":"")+("00000"+Math.abs(d)).slice(d>=0&&9999>=d?-4:-6),b=a.length;b--;)c=a[b],10>c&&(a[b]="0"+c);return d+"-"+a.slice(0,2).join("-")+"T"+a.slice(2).join(":")+"."+("000"+this.getUTCMilliseconds()).slice(-3)+"Z"});var F=!1;try{F=Date.prototype.toJSON&&null===new Date(0/0).toJSON()&&-1!==new Date(D).toJSON().indexOf(E)&&Date.prototype.toJSON.call({toISOString:function(){return!0}})}catch(G){}F||(Date.prototype.toJSON=function(){var a,b=Object(this),c=d(b);if("number"==typeof c&&!isFinite(c))return null;if(a=b.toISOString,"function"!=typeof a)throw new TypeError("toISOString property is not callable");return a.call(b)});var H=1e15===Date.parse("+033658-09-27T01:46:40.000Z"),I=!isNaN(Date.parse("2012-04-04T24:00:00.500Z"))||!isNaN(Date.parse("2012-11-31T23:59:59.000Z")),J=isNaN(Date.parse("2000-01-01T00:00:00.000Z"));(!Date.parse||J||I||!H)&&(Date=function(a){function b(c,d,e,f,g,h,i){var j=arguments.length;if(this instanceof a){var k=1===j&&String(c)===c?new a(b.parse(c)):j>=7?new a(c,d,e,f,g,h,i):j>=6?new a(c,d,e,f,g,h):j>=5?new a(c,d,e,f,g):j>=4?new a(c,d,e,f):j>=3?new a(c,d,e):j>=2?new a(c,d):j>=1?new a(c):new a;return k.constructor=b,k}return a.apply(this,arguments)}function c(a,b){var c=b>1?1:0;return f[b]+Math.floor((a-1969+c)/4)-Math.floor((a-1901+c)/100)+Math.floor((a-1601+c)/400)+365*(a-1970)}function d(b){return Number(new a(1970,0,1,0,0,0,b))}var e=new RegExp("^(\\d{4}|[+-]\\d{6})(?:-(\\d{2})(?:-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(?:(\\.\\d{1,}))?)?(Z|(?:([-+])(\\d{2}):(\\d{2})))?)?)?)?$"),f=[0,31,59,90,120,151,181,212,243,273,304,334,365];for(var g in a)b[g]=a[g];return b.now=a.now,b.UTC=a.UTC,b.prototype=a.prototype,b.prototype.constructor=b,b.parse=function(b){var f=e.exec(b);if(f){var g,h=Number(f[1]),i=Number(f[2]||1)-1,j=Number(f[3]||1)-1,k=Number(f[4]||0),l=Number(f[5]||0),m=Number(f[6]||0),n=Math.floor(1e3*Number(f[7]||0)),o=Boolean(f[4]&&!f[8]),p="-"===f[9]?1:-1,q=Number(f[10]||0),r=Number(f[11]||0);return(l>0||m>0||n>0?24:25)>k&&60>l&&60>m&&1e3>n&&i>-1&&12>i&&24>q&&60>r&&j>-1&&j=-864e13&&864e13>=g)?g:0/0}return a.parse.apply(this,arguments)},b}(Date)),Date.now||(Date.now=function(){return(new Date).getTime()}),Number.prototype.toFixed&&"0.000"===8e-5.toFixed(3)&&"0"!==.9.toFixed(0)&&"1.25"===1.255.toFixed(2)&&"1000000000000000128"===0xde0b6b3a7640080.toFixed(0)||!function(){function a(a,b){for(var c=-1;++c=0;)c+=h[b],h[b]=Math.floor(c/a),c=c%a*f}function c(){for(var a=g,b="";--a>=0;)if(""!==b||0===a||0!==h[a]){var c=String(h[a]);""===b?b=c:b+="0000000".slice(0,7-c.length)+c}return b}function d(a,b,c){return 0===b?c:b%2===1?d(a,b-1,c*a):d(a*a,b/2,c)}function e(a){for(var b=0;a>=4096;)b+=12,a/=4096;for(;a>=2;)b+=1,a/=2;return b}var f,g,h;f=1e7,g=6,h=[0,0,0,0,0,0],Number.prototype.toFixed=function(f){var g,h,i,j,k,l,m,n;if(g=Number(f),g=g!==g?0:Math.floor(g),0>g||g>20)throw new RangeError("Number.toFixed called with invalid number of decimals");if(h=Number(this),h!==h)return"NaN";if(-1e21>=h||h>=1e21)return String(h);if(i="",0>h&&(i="-",h=-h),j="0",h>1e-21)if(k=e(h*d(2,69,1))-69,l=0>k?h*d(2,-k,1):h/d(2,k,1),l*=4503599627370496,k=52-k,k>0){for(a(0,l),m=g;m>=7;)a(1e7,0),m-=7;for(a(d(10,m,1),0),m=k-1;m>=23;)b(1<<23),m-=23;b(1<0?(n=j.length,j=g>=n?i+"0.0000000000000000000".slice(0,g-n+2)+j:i+j.slice(0,n-g)+"."+j.slice(n-g)):j=i+j,j}}();var K=String.prototype.split;2!=="ab".split(/(?:ab)*/).length||4!==".".split(/(.?)(.?)/).length||"t"==="tesst".split(/(s)*/)[1]||"".split(/.?/).length||".".split(/()()/).length>1?!function(){var a=void 0===/()??/.exec("")[1];String.prototype.split=function(b,c){var d=this;if(void 0===b&&0===c)return[];if("[object RegExp]"!==Object.prototype.toString.call(b))return K.apply(this,arguments);var e,f,g,h,i=[],j=(b.ignoreCase?"i":"")+(b.multiline?"m":"")+(b.extended?"x":"")+(b.sticky?"y":""),k=0;for(b=new RegExp(b.source,j+"g"),d+="",a||(e=new RegExp("^"+b.source+"$(?!\\s)",j)),c=void 0===c?-1>>>0:c>>>0;(f=b.exec(d))&&(g=f.index+f[0].length,!(g>k&&(i.push(d.slice(k,f.index)),!a&&f.length>1&&f[0].replace(e,function(){for(var a=1;a1&&f.index=c)));)b.lastIndex===f.index&&b.lastIndex++;return k===d.length?(h||!b.test(""))&&i.push(""):i.push(d.slice(k)),i.length>c?i.slice(0,c):i}}():"0".split(void 0,0).length&&(String.prototype.split=function(a,b){return void 0===a&&0===b?[]:K.apply(this,arguments)});var L=String.prototype.replace,M=function(){var a=[];return"x".replace(/x(.)?/g,function(b,c){a.push(c)}),1===a.length&&"undefined"==typeof a[0]}();if(M||(String.prototype.replace=function(a,b){var c=l(b),d=m(a)&&/\)[*?]/.test(a.source);if(c&&d){var e=function(c){var d=arguments.length,e=a.lastIndex;a.lastIndex=0;var f=a.exec(c);return a.lastIndex=e,f.push(arguments[d-2],arguments[d-1]),b.apply(this,f)};return L.call(this,a,e)}return L.apply(this,arguments)}),"".substr&&"b"!=="0b".substr(-1)){var N=String.prototype.substr;String.prototype.substr=function(a,b){return N.call(this,0>a&&(a=this.length+a)<0?0:a,b)}}var O=" \n \f\r   ᠎              \u2028\u2029",P="​";if(!String.prototype.trim||O.trim()||!P.trim()){O="["+O+"]";var Q=new RegExp("^"+O+O+"*"),R=new RegExp(O+O+"*$");String.prototype.trim=function(){if(void 0===this||null===this)throw new TypeError("can't convert "+this+" to object");return String(this).replace(Q,"").replace(R,"")}}(8!==parseInt(O+"08")||22!==parseInt(O+"0x16"))&&(parseInt=function(a){var b=/^0[xX]/;return function(c,d){return c=String(c).trim(),Number(d)||(d=b.test(c)?16:10),a(c,d)}}(parseInt));var S=function(a){if(null==a)throw new TypeError("can't convert "+a+" to object");return Object(a)}}),function(){function a(b,d){function f(a){if(f[a]!==q)return f[a];var b;if("bug-string-char-index"==a)b="a"!="a"[0];else if("json"==a)b=f("json-stringify")&&f("json-parse");else{var c;if("json-stringify"==a){b=d.stringify;var e="function"==typeof b&&s;if(e){(c=function(){return 1}).toJSON=c;try{e="0"===b(0)&&"0"===b(new g)&&'""'==b(new h)&&b(r)===q&&b(q)===q&&b()===q&&"1"===b(c)&&"[1]"==b([c])&&"[null]"==b([q])&&"null"==b(null)&&"[null,null,null]"==b([q,r,null])&&'{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}'==b({a:[c,!0,!1,null,"\x00\b\n\f\r "]})&&"1"===b(null,c)&&"[\n 1,\n 2\n]"==b([1,2],null,1)&&'"-271821-04-20T00:00:00.000Z"'==b(new j(-864e13))&&'"+275760-09-13T00:00:00.000Z"'==b(new j(864e13))&&'"-000001-01-01T00:00:00.000Z"'==b(new j(-621987552e5))&&'"1969-12-31T23:59:59.999Z"'==b(new j(-1))}catch(i){e=!1}}b=e}if("json-parse"==a){if(b=d.parse,"function"==typeof b)try{if(0===b("0")&&!b(!1)){c=b('{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}');var k=5==c.a.length&&1===c.a[0];if(k){try{k=!b('" "')}catch(l){}if(k)try{k=1!==b("01")}catch(m){}if(k)try{k=1!==b("1.")}catch(n){}}}}catch(o){k=!1}b=k}}return f[a]=!!b}b||(b=e.Object()),d||(d=e.Object());var g=b.Number||e.Number,h=b.String||e.String,i=b.Object||e.Object,j=b.Date||e.Date,k=b.SyntaxError||e.SyntaxError,l=b.TypeError||e.TypeError,m=b.Math||e.Math,n=b.JSON||e.JSON;"object"==typeof n&&n&&(d.stringify=n.stringify,d.parse=n.parse);var o,p,q,i=i.prototype,r=i.toString,s=new j(-0xc782b5b800cec);try{s=-109252==s.getUTCFullYear()&&0===s.getUTCMonth()&&1===s.getUTCDate()&&10==s.getUTCHours()&&37==s.getUTCMinutes()&&6==s.getUTCSeconds()&&708==s.getUTCMilliseconds()}catch(t){}if(!f("json")){var u=f("bug-string-char-index");if(!s)var v=m.floor,w=[0,31,59,90,120,151,181,212,243,273,304,334],x=function(a,b){return w[b]+365*(a-1970)+v((a-1969+(b=+(b>1)))/4)-v((a-1901+b)/100)+v((a-1601+b)/400)};if((o=i.hasOwnProperty)||(o=function(a){var b,c={};return(c.__proto__=null,c.__proto__={toString:1},c).toString!=r?o=function(a){var b=this.__proto__;return a=a in(this.__proto__=null,this),this.__proto__=b,a}:(b=c.constructor,o=function(a){var c=(this.constructor||b).prototype;return a in this&&!(a in c&&this[a]===c[a])}),c=null,o.call(this,a)}),p=function(a,b){var d,e,f,g=0;(d=function(){this.valueOf=0}).prototype.valueOf=0,e=new d;for(f in e)o.call(e,f)&&g++;return d=e=null,g?p=2==g?function(a,b){var c,d={},e="[object Function]"==r.call(a);for(c in a)e&&"prototype"==c||o.call(d,c)||!(d[c]=1)||!o.call(a,c)||b(c)}:function(a,b){var c,d,e="[object Function]"==r.call(a);for(c in a)e&&"prototype"==c||!o.call(a,c)||(d="constructor"===c)||b(c);(d||o.call(a,c="constructor"))&&b(c)}:(e="valueOf toString toLocaleString propertyIsEnumerable isPrototypeOf hasOwnProperty constructor".split(" "),p=function(a,b){var d,f="[object Function]"==r.call(a),g=!f&&"function"!=typeof a.constructor&&c[typeof a.hasOwnProperty]&&a.hasOwnProperty||o;for(d in a)f&&"prototype"==d||!g.call(a,d)||b(d);for(f=e.length;d=e[--f];g.call(a,d)&&b(d));}),p(a,b)},!f("json-stringify")){var y={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"},z=function(a,b){return("000000"+(b||0)).slice(-a)},A=function(a){for(var b='"',c=0,d=a.length,e=!u||d>10,f=e&&(u?a.split(""):a);d>c;c++){var g=a.charCodeAt(c);switch(g){case 8:case 9:case 10:case 12:case 13:case 34:case 92:b+=y[g];break;default:if(32>g){b+="\\u00"+z(2,g.toString(16));break}b+=e?f[c]:a.charAt(c)}}return b+'"'},B=function(a,b,c,d,e,f,g){var h,i,j,k,m,n,s,t,u;try{h=b[a]}catch(w){}if("object"==typeof h&&h)if(i=r.call(h),"[object Date]"!=i||o.call(h,"toJSON"))"function"==typeof h.toJSON&&("[object Number]"!=i&&"[object String]"!=i&&"[object Array]"!=i||o.call(h,"toJSON"))&&(h=h.toJSON(a));else if(h>-1/0&&1/0>h){if(x){for(k=v(h/864e5),i=v(k/365.2425)+1970-1;x(i+1,0)<=k;i++);for(j=v((k-x(i,0))/30.42);x(i,j+1)<=k;j++);k=1+k-x(i,j),m=(h%864e5+864e5)%864e5,n=v(m/36e5)%24,s=v(m/6e4)%60,t=v(m/1e3)%60,m%=1e3}else i=h.getUTCFullYear(),j=h.getUTCMonth(),k=h.getUTCDate(),n=h.getUTCHours(),s=h.getUTCMinutes(),t=h.getUTCSeconds(),m=h.getUTCMilliseconds();h=(0>=i||i>=1e4?(0>i?"-":"+")+z(6,0>i?-i:i):z(4,i))+"-"+z(2,j+1)+"-"+z(2,k)+"T"+z(2,n)+":"+z(2,s)+":"+z(2,t)+"."+z(3,m)+"Z"}else h=null;if(c&&(h=c.call(b,a,h)),null===h)return"null";if(i=r.call(h),"[object Boolean]"==i)return""+h;if("[object Number]"==i)return h>-1/0&&1/0>h?""+h:"null";if("[object String]"==i)return A(""+h);if("object"==typeof h){for(a=g.length;a--;)if(g[a]===h)throw l();if(g.push(h),u=[],b=f,f+=e,"[object Array]"==i){for(j=0,a=h.length;a>j;j++)i=B(j,h,c,d,e,f,g),u.push(i===q?"null":i);a=u.length?e?"[\n"+f+u.join(",\n"+f)+"\n"+b+"]":"["+u.join(",")+"]":"[]"}else p(d||h,function(a){var b=B(a,h,c,d,e,f,g);b!==q&&u.push(A(a)+":"+(e?" ":"")+b)}),a=u.length?e?"{\n"+f+u.join(",\n"+f)+"\n"+b+"}":"{"+u.join(",")+"}":"{}";return g.pop(),a}};d.stringify=function(a,b,d){var e,f,g,h;if(c[typeof b]&&b)if("[object Function]"==(h=r.call(b)))f=b;else if("[object Array]"==h){g={};for(var i,j=0,k=b.length;k>j;i=b[j++],h=r.call(i),("[object String]"==h||"[object Number]"==h)&&(g[i]=1));}if(d)if("[object Number]"==(h=r.call(d))){if(0<(d-=d%1))for(e="",d>10&&(d=10);e.length=d.length?d:d.slice(0,10));return B("",(i={},i[""]=a,i),f,g,e,"",[])}}if(!f("json-parse")){var C,D,E=h.fromCharCode,F={92:"\\",34:'"',47:"/",98:"\b",116:" ",110:"\n",102:"\f",114:"\r"},G=function(){throw C=D=null,k()},H=function(){for(var a,b,c,d,e,f=D,g=f.length;g>C;)switch(e=f.charCodeAt(C)){case 9:case 10:case 13:case 32:C++;break;case 123:case 125:case 91:case 93:case 58:case 44:return a=u?f.charAt(C):f[C],C++,a;case 34:for(a="@",C++;g>C;)if(e=f.charCodeAt(C),32>e)G();else if(92==e)switch(e=f.charCodeAt(++C)){case 92:case 34:case 47:case 98:case 116:case 110:case 102:case 114:a+=F[e],C++;break;case 117:for(b=++C,c=C+4;c>C;C++)e=f.charCodeAt(C),e>=48&&57>=e||e>=97&&102>=e||e>=65&&70>=e||G();a+=E("0x"+f.slice(b,C));break;default:G()}else{if(34==e)break;for(e=f.charCodeAt(C),b=C;e>=32&&92!=e&&34!=e;)e=f.charCodeAt(++C);a+=f.slice(b,C)}if(34==f.charCodeAt(C))return C++,a;G();default:if(b=C,45==e&&(d=!0,e=f.charCodeAt(++C)),e>=48&&57>=e){for(48==e&&(e=f.charCodeAt(C+1),e>=48&&57>=e)&&G();g>C&&(e=f.charCodeAt(C),e>=48&&57>=e);C++);if(46==f.charCodeAt(C)){for(c=++C;g>c&&(e=f.charCodeAt(c),e>=48&&57>=e);c++);c==C&&G(),C=c}if(e=f.charCodeAt(C),101==e||69==e){for(e=f.charCodeAt(++C),43!=e&&45!=e||C++,c=C;g>c&&(e=f.charCodeAt(c),e>=48&&57>=e);c++);c==C&&G(),C=c}return+f.slice(b,C)}if(d&&G(),"true"==f.slice(C,C+4))return C+=4,!0;if("false"==f.slice(C,C+5))return C+=5,!1;if("null"==f.slice(C,C+4))return C+=4,null;G()}return"$"},I=function(a){var b,c;if("$"==a&&G(),"string"==typeof a){if("@"==(u?a.charAt(0):a[0]))return a.slice(1);if("["==a){for(b=[];a=H(),"]"!=a;c||(c=!0))c&&(","==a?(a=H(),"]"==a&&G()):G()),","==a&&G(),b.push(I(a));return b}if("{"==a){for(b={};a=H(),"}"!=a;c||(c=!0))c&&(","==a?(a=H(),"}"==a&&G()):G()),","!=a&&"string"==typeof a&&"@"==(u?a.charAt(0):a[0])&&":"==H()||G(),b[a.slice(1)]=I(H());return b}G()}return a},J=function(a,b,c){c=K(a,b,c),c===q?delete a[b]:a[b]=c},K=function(a,b,c){var d,e=a[b];if("object"==typeof e&&e)if("[object Array]"==r.call(e))for(d=e.length;d--;)J(e,d,c);else p(e,function(a){J(e,a,c)});return c.call(a,b,e)};d.parse=function(a,b){var c,d;return C=0,D=""+a,c=I(H()),"$"!=H()&&G(),C=D=null,b&&"[object Function]"==r.call(b)?K((d={},d[""]=c,d),"",b):c}}}return d.runInContext=a,d}var b="function"==typeof define&&define.amd,c={"function":!0,object:!0},d=c[typeof exports]&&exports&&!exports.nodeType&&exports,e=c[typeof window]&&window||this,f=d&&c[typeof module]&&module&&!module.nodeType&&"object"==typeof global&&global;if(!f||f.global!==f&&f.window!==f&&f.self!==f||(e=f),d&&!b)a(e,d);else{var g=e.JSON,h=e.JSON3,i=!1,j=a(e,e.JSON3={noConflict:function(){return i||(i=!0,e.JSON=g,e.JSON3=h,g=h=null),j}});e.JSON={parse:j.parse,stringify:j.stringify}}b&&define(function(){return j})}.call(this); \ No newline at end of file diff --git a/public/scripts/scripts.433fc63d.js b/public/scripts/scripts.433fc63d.js deleted file mode 100644 index 1c08443..0000000 --- a/public/scripts/scripts.433fc63d.js +++ /dev/null @@ -1 +0,0 @@ -(function(){"use strict";angular.module("blicblockApp",["ngAnimate","ngCookies","ngResource","ngRoute","ngSanitize","ngTouch","ui.bootstrap","LocalStorageModule","angularMoment","swipe"]).config(["$routeProvider","localStorageServiceProvider",function(a,b){var c;return b.setPrefix("blicblockJS"),a.when("/",{templateUrl:"views/main.html",controller:"MainCtrl",title:"Play"}).when("/test/:color_count",{templateUrl:"views/main.html",controller:"MainCtrl",title:"Test"}).when("/test/cascade/:cascade_count",{templateUrl:"views/main.html",controller:"MainCtrl",title:"Test Cascades"}).when("/about",{templateUrl:"views/about.html",controller:"AboutCtrl",title:"About"}).when("/help",{templateUrl:"views/help.html",controller:"AboutCtrl",title:"How to Play"}),c={templateUrl:"views/scores.html",controller:"ScoresCtrl",title:"High Scores"},a.when("/scores",c).when("/scores/country/:country_code",c).when("/scores/initials/:initials",c).when("/scores/time/:time",c).when("/scores/order/:order",c).when("/scores/page/:page",c).when("/scores/country/:country_code/page/:page",c).when("/scores/initials/:initials/page/:page",c).when("/scores/time/:time/page/:page",c).when("/scores/order/:order/page/:page",c).when("/scores/country/:country_code/order/:order",c).when("/scores/country/:country_code/order/:order/page/:page",c).when("/scores/country/:country_code/initials/:initials",c).when("/scores/country/:country_code/initials/:initials/page/:page",c).when("/scores/country/:country_code/initials/:initials/order/:order",c).when("/scores/country/:country_code/initials/:initials/order/:order/page/:page",c).when("/scores/country/:country_code/time/:time",c).when("/scores/country/:country_code/time/:time/page/:page",c).when("/scores/country/:country_code/time/:time/order/:order",c).when("/scores/country/:country_code/time/:time/order/:order/page/:page",c).when("/scores/country/:country_code/initials/:initials/time/:time",c).when("/scores/country/:country_code/initials/:initials/time/:time/page/:page",c).when("/scores/country/:country_code/initials/:initials/time/:time/order/:order",c).when("/scores/country/:country_code/initials/:initials/time/:time/order/:order/page/:page",c).when("/scores/initials/:initials/order/:order",c).when("/scores/initials/:initials/order/:order/page/:page",c).when("/scores/initials/:initials/time/:time",c).when("/scores/initials/:initials/time/:time/page/:page",c).when("/scores/initials/:initials/time/:time/order/:order",c).when("/scores/initials/:initials/time/:time/order/:order/page/:page",c).when("/scores/time/:time/order/:order",c).when("/scores/time/:time/order/:order/page/:page",c),a.otherwise({redirectTo:"/"})}]).filter("reverse",function(){return function(a){return a.slice().reverse()}}).filter("range",function(){return function(a,b){var c,d;for(b=parseInt(b,10),c=d=0;b>=0?b>d:d>b;c=b>=0?++d:--d)a.push(c);return a}}).run(["$location","$rootScope",function(a,b){return b.$on("$routeChangeSuccess",function(a,c){return c.hasOwnProperty("$$route")?(b.collapse.nav=!0,b.title=c.$$route.title):void 0})}])}).call(this),function(){var a;a=function(){function a(a){this.color=a.color,this.x=a.x,this.y=a.y,this.locked="undefined"==typeof a.locked?!1:a.locked,this.active="undefined"==typeof a.active?!0:a.active,this.sliding=!1,this.plummetting=!1,this.highlight=!1,this.id="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var b,c;return b=16*Math.random()|0,c="x"===a?b:3&b|8,c.toString(16)})}return a}(),("undefined"!=typeof exports&&null!==exports?exports:this).Block=a}.call(this),function(){"use strict";angular.module("blicblockApp").directive("ngLeft",function(){return function(a,b,c){var d;return d=void 0,b.bind("keydown keypress",function(b){return $(b.target).is("input")||!b.which||d&&d.which===b.which?void 0:(d=b,37===b.which||"a"===String.fromCharCode(b.which)?(a.$apply(function(){return a.$eval(c.ngLeft)}),b.preventDefault()):void 0)}),b.bind("keyup",function(){return d=void 0})}})}.call(this),function(){"use strict";angular.module("blicblockApp").directive("ngRight",function(){return function(a,b,c){var d;return d=void 0,b.bind("keydown keypress",function(b){return $(b.target).is("input")||!b.which||d&&d.which===b.which?void 0:(d=b,39===b.which||"d"===String.fromCharCode(b.which)?(a.$apply(function(){return a.$eval(c.ngRight)}),b.preventDefault()):void 0)}),b.bind("keyup",function(){return d=void 0})}})}.call(this),function(){"use strict";angular.module("blicblockApp").directive("ngDown",function(){return function(a,b,c){return b.bind("keydown keypress",function(b){return!$(b.target).is("input")&&b.which&&(40===b.which||"s"===String.fromCharCode(b.which))?(a.$apply(function(){return a.$eval(c.ngDown)}),b.preventDefault()):void 0})}})}.call(this),function(){"use strict";angular.module("blicblockApp").directive("ngSpace",function(){return function(a,b,c){return b.bind("keydown keypress",function(b){return b.which&&32===b.which?(a.$apply(function(){return a.$eval(c.ngSpace)}),b.preventDefault()):void 0})}})}.call(this),function(){"use strict";angular.module("blicblockApp").directive("ngTap",function(){return{link:function(a,b,c){var d;return d=!1,b.bind("touchstart",function(){return b.addClass("active"),d=!0,!0}),b.bind("touchmove",function(){return b.removeClass("active"),d=!1,!0}),b.bind("touchend",function(){return b.removeClass("active"),d&&a.$apply(c.ngTap,b),!0})}}})}.call(this),function(){"use strict";angular.module("blicblockApp").service("Config",function(){var a;return new(a=function(){function a(){this.storage_keys={high_score:"high_score",initials:"initials"}}return a}())})}.call(this),function(){"use strict";angular.module("blicblockApp").service("Tetromino",["$rootScope","$interval","$timeout",function(a,b,c){var d;return new(d=function(){function a(){this.blocks=[],this.upcoming=[],this.colors=["magenta","orange","yellow","green","blue","white"],this.info={cols:5,rows:7,score_value:1e3,checking:!1,in_progress:!0,plummetting_block:!1,sliding_block:!1,game_over:!1,current_score:0,tick_length_decrement_pct:.09,tick_length:1200,level:1,test_mode:!1,submitted_score:!1},this.info.middle_col_idx=(this.info.cols-1)/2}return a.prototype.remove_all_blocks=function(){var a,b;for(a=this.blocks.length-1,b=[];a>=0;)this.blocks.splice(a,1),b.push(a--);return b},a.prototype.add_locked_block=function(a,b,c){return this.blocks.push(new Block({color:a,x:b,y:c,locked:!0,active:!1}))},a.prototype.setup_cascade=function(){return this.info.in_progress=!1,this.remove_all_blocks()},a.prototype.setup_one_cascade=function(){var a,b,c;return this.setup_cascade(),c=this.info.rows-1,a=this.colors[0],b=this.colors[1],this.add_locked_block(a,c,0),this.add_locked_block(a,c,1),this.add_locked_block(a,c,2),this.add_locked_block(b,c-1,0),this.add_locked_block(b,c-1,1),this.add_locked_block(b,c-1,2),this.add_locked_block(a,c-2,0),this.upcoming[0]=new Block({color:b}),this.info.in_progress=!0},a.prototype.setup_two_cascades=function(){var a,b;return this.setup_one_cascade(),a=this.colors[2],b=this.info.rows-1,this.add_locked_block(a,b-4,0),this.add_locked_block(a,b-3,0),this.add_locked_block(a,b-5,0),this.add_locked_block(a,b-2,1)},a.prototype.setup_three_cascades=function(){var a,b;return this.setup_two_cascades(),a=this.colors[3],b=this.info.rows-1,this.add_locked_block(a,b-6,0),this.add_locked_block(a,b-3,1),this.add_locked_block(a,b-4,1),this.add_locked_block(a,b-5,1)},a.prototype.setup_four_cascades=function(){var a,b,c,d;return this.setup_cascade(),d=this.info.rows-1,c=this.info.cols-1,a=this.colors[0],b=this.colors[1],this.add_locked_block(a,d,c),this.add_locked_block(b,d,c-1),this.add_locked_block(b,d,c-2),this.add_locked_block(b,d,c-3),this.add_locked_block(b,d-1,c),this.add_locked_block(a,d-1,c-1),this.add_locked_block(a,d-1,c-2),this.add_locked_block(a,d-1,c-3),this.add_locked_block(a,d-2,c),this.add_locked_block(b,d-2,c-1),this.add_locked_block(b,d-2,c-2),this.add_locked_block(b,d-2,c-3),this.add_locked_block(b,d-3,c),this.add_locked_block(a,d-3,c-1),this.add_locked_block(a,d-3,c-2),this.add_locked_block(a,d-3,c-3),this.add_locked_block(a,d-4,c),this.add_locked_block(b,d-4,c-1),this.add_locked_block(b,d-4,c-2),this.add_locked_block(b,d-4,c-3),this.upcoming[0]=new Block({color:b}),this.info.in_progress=!0},a.prototype.get_active_block=function(){return this.blocks.filter(function(a){return a.active})[0]},a.prototype.get_middle_column_blocks=function(){return this.blocks.filter(function(a){return function(b){return b.y===a.info.middle_col_idx}}(this))},a.prototype.check_for_tetrominos=function(){var a,b,c,d;if(this.info.in_progress&&!this.info.checking){for(this.info.checking=!0,d=this.blocks,b=0,c=d.length;c>b;b++)a=d[b],a&&a.locked&&!a.active&&(this.check_for_straight_tetromino(a),this.check_for_square_tetromino(a),this.check_for_l_tetromino(a),this.check_for_z_tetromino(a),this.check_for_t_tetromino(a));return this.info.checking=!1}},a.prototype.get_closest_block_to_left=function(a,b){var c;return c=this.blocks.filter(function(c){return c.x===a&&c.yb.y?1:0}),c[c.length-1]},a.prototype.get_closest_block_to_right=function(a,b){var c;return c=this.blocks.filter(function(c){return c.x===a&&c.y>b}),c.sort(function(a,b){return a.yb.y?1:0}),c[0]},a.prototype.get_closest_block_below=function(a,b){var c;return c=this.blocks.filter(function(c){return c.x>a&&c.y===b}),c.sort(function(a,b){return a.xb.x?1:0}),c[0]},a.prototype.is_block_directly_below=function(a,b){return this.blocks.filter(function(c){return c.x===a+1&&c.y===b})[0]},a.prototype.plummet_block=function(a,c,d){var e,f;return a.x===c?void(d&&d()):(f=void 0,e=function(e){return function(){return a.plummetting=!0,e.info.plummetting_block=!0,a.xc;c++)b=a[c],e.push(b.x);return e}(),f=function(){var c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(b.y);return e}(),c=Math.max.apply(Math,e),d=this.blocks.filter(function(a){return a.x-1}),d.sort(function(a,b){return a.xb.x?-1:0}),d},a.prototype.highlight=function(a){return a.highlight=!0,c(function(){return a.highlight=!1},.21*this.info.tick_length)},a.prototype.drop_blocks=function(){var a,b,c,d,e,f;if(this.info.in_progress){for(b=this.info.rows-1,e=this.blocks,f=[],c=0,d=e.length;d>c;c++)a=e[c],a&&(a.sliding||((a.active||!a.locked)&&(a.x===b&&(a.locked=!0,a.active=!1,this.on_block_land(a)),this.is_block_directly_below(a.x,a.y)&&(a.locked=!0,a.active=!1,this.on_block_land(a))),a.locked||f.push(a.x++)));return f}},a.prototype.on_block_land=function(a){return this.highlight(a),this.check_for_tetrominos()},a.prototype.increment_level_if_necessary=function(){return this.info.current_score%4e3===0?this.info.level++:void 0},a.prototype.plummet_blocks=function(a,b){var c,d,e;return"undefined"==typeof b&&(b=0),c=a[b],d=this.get_closest_block_below(c.x,c.y),e=d?d.x-1:this.info.rows-1,this.plummet_block(c,e,function(c){return function(){return b=0;)b.indexOf(this.blocks[c].id)>-1&&this.blocks.splice(c,1),c--;return this.info.current_score+=this.info.score_value,this.increment_level_if_necessary(),d=this.blocks_on_top(a),d=d.filter(function(a){return!a.active}),d.length>0&&this.plummet_blocks(d),this.check_for_tetrominos()}},a.prototype.lookup=function(a,b,c){return a>=this.info.rows||b>=this.info.cols?void 0:this.blocks.filter(function(d){return d.x===a&&d.y===b&&d.color===c})[0]},a.prototype.check_for_straight_tetromino=function(a){return this.check_for_straight_hor_tetromino(a),this.check_for_straight_ver_tetromino(a)},a.prototype.check_for_straight_hor_tetromino=function(a){var b,c,d,e,f,g;return g=a.y,f=a.x,e=a.color,b=this.lookup(f,g+1,e),b&&(c=this.lookup(f,g+2,e),c&&(d=this.lookup(f,g+3,e)))?this.remove_blocks([a,b,c,d]):void 0},a.prototype.check_for_straight_ver_tetromino=function(a){var b,c,d,e,f,g;return f=a.x,g=a.y,e=a.color,b=this.lookup(f+1,g,e),b&&(c=this.lookup(f+2,g,e),c&&(d=this.lookup(f+3,g,e)))?this.remove_blocks([a,b,c,d]):void 0},a.prototype.check_for_square_tetromino=function(a){var b,c,d,e,f,g;return f=a.x,g=a.y,e=a.color,b=this.lookup(f,g+1,e),b&&(c=this.lookup(f+1,g,e),c&&(d=this.lookup(f+1,g+1,e)))?this.remove_blocks([a,b,c,d]):void 0},a.prototype.check_for_l_tetromino=function(a){return this.check_for_left_l_tetromino(a),this.check_for_right_l_tetromino(a)},a.prototype.check_for_left_l_tetromino=function(a){var b,c,d,e,f,g;if(f=a.x,g=a.y,e=a.color,b=this.lookup(f+1,g,e)){if(c=this.lookup(f+2,g,e)){if(d=this.lookup(f+2,g-1,e))return this.remove_blocks([a,b,c,d]);if(d=this.lookup(f,g+1,e),!d)return;return this.remove_blocks([a,b,c,d])}if(c=this.lookup(f+1,g+1,e)){if(d=this.lookup(f+1,g+2,e),!d)return;return this.remove_blocks([a,b,c,d])}if((c=this.lookup(f,g-1,e))&&(d=this.lookup(f,g-2,e)))return this.remove_blocks([a,b,c,d])}},a.prototype.check_for_right_l_tetromino=function(a){var b,c,d,e,f,g;if(f=a.x,g=a.y,e=a.color,b=this.lookup(f+1,g,e)){if(c=this.lookup(f+2,g,e)){if(d=this.lookup(f+2,g+1,e))return this.remove_blocks([a,b,c,d]);if(d=this.lookup(f,g-1,e),!d)return;return this.remove_blocks([a,b,c,d])}if(c=this.lookup(f+1,g-1,e)){if(d=this.lookup(f+1,g-2,e),!d)return;return this.remove_blocks([a,b,c,d])}if((c=this.lookup(f,g+1,e))&&(d=this.lookup(f,g+2,e)))return this.remove_blocks([a,b,c,d])}},a.prototype.check_for_z_tetromino=function(a){var b,c,d,e,f,g;if(f=a.x,g=a.y,e=a.color,b=this.lookup(f+1,g,e)){if(c=this.lookup(f,g-1,e)){if(d=this.lookup(f+1,g+1,e),!d)return;return this.remove_blocks([a,b,c,d])}if(c=this.lookup(f,g+1,e)){if(d=this.lookup(f+1,g-1,e),!d)return;return this.remove_blocks([a,b,c,d])}if(c=this.lookup(f+1,g+1,e)){if(d=this.lookup(f+2,g+1,e),!d)return;return this.remove_blocks([a,b,c,d])}if((c=this.lookup(f+1,g-1,e))&&(d=this.lookup(f+2,g-1,e)))return this.remove_blocks([a,b,c,d])}},a.prototype.check_for_t_tetromino=function(a){var b,c,d,e,f,g;if(f=a.x,g=a.y,e=a.color,b=this.lookup(f+1,g,e)){if(c=this.lookup(f+2,g,e)){if(d=this.lookup(f+1,g+1,e))return this.remove_blocks([a,b,c,d]);if(d=this.lookup(f+1,g-1,e),!d)return;return this.remove_blocks([a,b,c,d])}if(c=this.lookup(f+1,g-1,e)){if(d=this.lookup(f+1,g+1,e),!d)return;return this.remove_blocks([a,b,c,d])}if((c=this.lookup(f,g-1,e))&&(d=this.lookup(f,g+1,e)))return this.remove_blocks([a,b,c,d])}},a}())}])}.call(this),function(){"use strict";angular.module("blicblockApp").factory("Country",["$resource",function(a){return a("/api/scores/countries.json",{},{})}])}.call(this),function(){"use strict";angular.module("blicblockApp").factory("Score",["$resource",function(a){return a("/api/scores/:id.json",{},{query:{method:"GET",isArray:!1},update:{method:"PUT"}})}])}.call(this),function(){"use strict";angular.module("blicblockApp").service("Notification",["$timeout",function(a){var b;return new(b=function(){function b(){this.notices=[],this.errors=[]}return b.prototype.wipe_notices=function(){var a,b,c,d,e;for(d=this.notices,e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(this.remove("notice",a.id));return e},b.prototype.wipe_errors=function(){var a,b,c,d,e;for(d=this.errors,e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(this.remove("error",a.id));return e},b.prototype.wipe_notifications=function(){return this.wipe_notices(),this.wipe_errors()},b.prototype.remove=function(a,b){var c,d,e,f,g,h,i;if("error"===a){f=this.errors,h=[];for(d in f)c=f[d],c.id===b&&h.push(this.errors.splice(d,1));return h}g=this.notices,i=[];for(d in g)e=g[d],e.id===b&&i.push(this.notices.splice(d,1));return i},b.prototype.error=function(b){var c;if(b)return console.error(b),c=this.errors.length+1,this.errors.push({message:b,id:c}),a(function(a){return function(){return a.remove("error",c)}}(this),3500)},b.prototype.notice=function(b){var c;if(b)return c=this.notices.length+1,this.notices.push({message:b,id:c}),a(function(a){return function(){return a.remove("notice",c)}}(this),3e3)},b}())}])}.call(this),function(){"use strict";angular.module("blicblockApp").controller("NotificationsCtrl",["$scope","$cookieStore","Notification",function(a,b,c){var d;return a.notices=c.notices,a.errors=c.errors,a.remove=function(a,b){return c.remove(a,b)},d=b.get("error"),d?(c.error(d),b.remove("error")):void 0}])}.call(this),function(){"use strict";angular.module("blicblockApp").controller("MainCtrl",["$scope","$window","$timeout","$interval","$routeParams","$rootScope","localStorageService","Config","Tetromino","Score","Notification",function(a,b,c,d,e,f,g,h,i,j,k){var l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;if(a.blocks=i.blocks,a.upcoming=i.upcoming,a.game_info=i.info,a.score_record=new j,a.new_high_score={},r=void 0,a.new_game=function(){return a.score_record=new j,b.location.reload()},e.color_count?(n=parseInt(e.color_count,10),n>i.colors.length&&(n=i.colors.length-1),1>n&&(n=1),o=i.colors.slice(0,n)):(o=i.colors,a.game_info.test_mode&&a.new_game()),u=function(){return o[Math.floor(Math.random()*o.length)]},e.cascade_count){switch(m=parseInt(e.cascade_count,10)){case 1:i.setup_one_cascade();break;case 2:i.setup_two_cascades();break;case 3:i.setup_three_cascades();break;default:i.setup_four_cascades()}a.upcoming.push(new Block({color:u()}))}else for(a.game_info.test_mode&&a.new_game();a.upcoming.length<2;)a.upcoming.push(new Block({color:u()}));return a.game_info.test_mode=!!e.color_count||!!e.cascade_count,v=function(){var b;return a.game_info.test_mode?{value:-1e6,date:new Date(1969,0,1)}:(b=g.get(h.storage_keys.high_score),b?b:{})},a.existing_high_score=v(),l=function(){return angular.isDefined(r)?(d.cancel(r),r=void 0):void 0},x=function(){var b,c;if(!a.game_info.test_mode)return c=g.get(h.storage_keys.high_score),b=a.game_info.current_score,a.score_record.value=b,a.score_record.initials=g.get(h.storage_keys.initials),c&&c.value1&&(a.game_info.tick_length-=a.game_info.tick_length*a.game_info.tick_length_decrement_pct,l()),y()}),a.$on("$locationChangeStart",function(){return a.$emit("pause")}),f.$watch("collapse.nav",function(){return f.collapse.nav?void 0:a.$emit("pause")}),a.capitalize_initials=function(){return a.score_record.initials?a.score_record.initials=a.score_record.initials.toUpperCase():void 0}}])}.call(this),function(){"use strict";angular.module("blicblockApp").controller("AboutCtrl",function(){})}.call(this),function(){"use strict";angular.module("blicblockApp").controller("ScoresCtrl",["$scope","$location","$routeParams","Score","Country",function(a,b,c,d,e){var f,g,h,i;return a.countries=e.query(),i="week",g="",f="",h="value",a.filters={time:c.time||i,initials:c.initials||g,order:c.order||h,country_code:c.country_code||f,page:c.page||1},a.score_results=d.query(a.filters),a.filter=function(){var c,d;return d="/scores",c=a.filters.country_code,c&&c!==f&&(d+="/country/"+c),a.filters.initials!==g&&(d+="/initials/"+a.filters.initials),a.filters.time!==i&&(d+="/time/"+a.filters.time),a.filters.order!==h&&(d+="/order/"+a.filters.order),1!==a.filters.page&&(d+="/page/"+a.filters.page),b.path(d)},a.change_page=function(){return a.filters.page=a.score_results.page,a.filter()}}])}.call(this); \ No newline at end of file diff --git a/public/scripts/vendor.667a6f66.js b/public/scripts/vendor.667a6f66.js deleted file mode 100644 index 011225c..0000000 --- a/public/scripts/vendor.667a6f66.js +++ /dev/null @@ -1,12 +0,0 @@ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){function c(a){var b=a.length,c=_.type(a);return"function"===c||_.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}function d(a,b,c){if(_.isFunction(b))return _.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return _.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(hb.test(b))return _.filter(b,a,c);b=_.filter(b,a)}return _.grep(a,function(a){return U.call(b,a)>=0!==c})}function e(a,b){for(;(a=a[b])&&1!==a.nodeType;);return a}function f(a){var b=ob[a]={};return _.each(a.match(nb)||[],function(a,c){b[c]=!0}),b}function g(){Z.removeEventListener("DOMContentLoaded",g,!1),a.removeEventListener("load",g,!1),_.ready()}function h(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=_.expando+Math.random()}function i(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(ub,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:tb.test(c)?_.parseJSON(c):c}catch(e){}sb.set(a,b,c)}else c=void 0;return c}function j(){return!0}function k(){return!1}function l(){try{return Z.activeElement}catch(a){}}function m(a,b){return _.nodeName(a,"table")&&_.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function n(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function o(a){var b=Kb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function p(a,b){for(var c=0,d=a.length;d>c;c++)rb.set(a[c],"globalEval",!b||rb.get(b[c],"globalEval"))}function q(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(rb.hasData(a)&&(f=rb.access(a),g=rb.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)_.event.add(b,e,j[e][c])}sb.hasData(a)&&(h=sb.access(a),i=_.extend({},h),sb.set(b,i))}}function r(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&_.nodeName(a,b)?_.merge([a],c):c}function s(a,b){var c=b.nodeName.toLowerCase();"input"===c&&yb.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}function t(b,c){var d,e=_(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:_.css(e[0],"display");return e.detach(),f}function u(a){var b=Z,c=Ob[a];return c||(c=t(a,b),"none"!==c&&c||(Nb=(Nb||_("
My Sim Marly provides an instructional video on how to play Blicblock.
\ No newline at end of file diff --git a/public/views/main.html b/public/views/main.html deleted file mode 100644 index 649cd77..0000000 --- a/public/views/main.html +++ /dev/null @@ -1 +0,0 @@ -
{{game_info.current_score}}
{{game_info.level}}

game over

Final score: {{game_info.current_score}}
Test mode, score is not saved.
New personal high score!
Your high score: {{existing_high_score.value}}

paused

Test mode, score will not be saved.
\ No newline at end of file diff --git a/public/views/scores.html b/public/views/scores.html deleted file mode 100644 index ec4756d..0000000 --- a/public/views/scores.html +++ /dev/null @@ -1 +0,0 @@ -

Rank Score Name
{{score.rank}} {{score.value}} {{score.initials}} {{score.country}}
\ No newline at end of file diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb new file mode 100644 index 0000000..e672b25 --- /dev/null +++ b/spec/controllers/home_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe HomeController, type: :controller do + +end diff --git a/spec/javascripts/helpers/react_helper.js b/spec/javascripts/helpers/react_helper.js new file mode 100644 index 0000000..751354b --- /dev/null +++ b/spec/javascripts/helpers/react_helper.js @@ -0,0 +1 @@ +window.TestUtils = React.addons.TestUtils diff --git a/spec/javascripts/models/tetromino_checker_spec.es6.js b/spec/javascripts/models/tetromino_checker_spec.es6.js new file mode 100644 index 0000000..6a1dfcc --- /dev/null +++ b/spec/javascripts/models/tetromino_checker_spec.es6.js @@ -0,0 +1,200 @@ +describe('TetrominoChecker', () => { + it('starts with no tetromino blocks', () => { + const checker = new TetrominoChecker([], 0) + expect(checker.tetromino).toEqual([]) + }) + + it('returns false when no blocks are given', () => { + const checker = new TetrominoChecker([], 0) + expect(checker.check()).toEqual(false) + }) + + it('filters out blocks of a different color', () => { + const b1 = new Block({ x: 0, y: 0, color: 'blue' }) + const b2 = new Block({ x: 0, y: 1, color: 'magenta' }) + const b3 = new Block({ x: 0, y: 2, color: 'white' }) + const b4 = new Block({ x: 0, y: 3, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.blocks.length).toEqual(1) + expect(checker.blocks[0].id).toEqual(b4.id) + }) + + // 1*** + it('returns true when straight horizontal tetromino exists', () => { + const b1 = new Block({ x: 0, y: 0, color: 'blue' }) + const b2 = new Block({ x: 0, y: 1, color: 'blue' }) + const b3 = new Block({ x: 0, y: 2, color: 'blue' }) + const b4 = new Block({ x: 0, y: 3, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + // 1 + // * + // * + // * + it('returns true when straight vertical tetromino exists', () => { + const b1 = new Block({ x: 0, y: 0, color: 'blue' }) + const b2 = new Block({ x: 1, y: 0, color: 'blue' }) + const b3 = new Block({ x: 2, y: 0, color: 'blue' }) + const b4 = new Block({ x: 3, y: 0, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + // 1* + // ** + it('returns true when square tetromino exists', () => { + const b1 = new Block({ x: 0, y: 0, color: 'blue' }) + const b2 = new Block({ x: 0, y: 1, color: 'blue' }) + const b3 = new Block({ x: 1, y: 0, color: 'blue' }) + const b4 = new Block({ x: 1, y: 1, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + // 1 1* + // * 1 * **1 + // ** *** * * + // A B C D + it('returns true when left L A tetromino exists', () => { + const b1 = new Block({ x: 0, y: 1, color: 'blue' }) + const b2 = new Block({ x: 1, y: 1, color: 'blue' }) + const b3 = new Block({ x: 2, y: 0, color: 'blue' }) + const b4 = new Block({ x: 2, y: 1, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + it('returns true when left L B tetromino exists', () => { + const b1 = new Block({ x: 0, y: 0, color: 'blue' }) + const b2 = new Block({ x: 1, y: 0, color: 'blue' }) + const b3 = new Block({ x: 1, y: 1, color: 'blue' }) + const b4 = new Block({ x: 1, y: 2, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + it('returns true when left L C tetromino exists', () => { + const b1 = new Block({ x: 0, y: 0, color: 'blue' }) + const b2 = new Block({ x: 0, y: 1, color: 'blue' }) + const b3 = new Block({ x: 1, y: 0, color: 'blue' }) + const b4 = new Block({ x: 2, y: 0, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + it('returns true when left L D tetromino exists', () => { + const b1 = new Block({ x: 0, y: 2, color: 'blue' }) + const b2 = new Block({ x: 1, y: 2, color: 'blue' }) + const b3 = new Block({ x: 0, y: 1, color: 'blue' }) + const b4 = new Block({ x: 0, y: 0, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + // 1 *1 + // * 1 * 1** + // ** *** * * + // A B C D + it('returns true when right L A tetromino exists', () => { + const b1 = new Block({ x: 0, y: 0, color: 'blue' }) + const b2 = new Block({ x: 1, y: 0, color: 'blue' }) + const b3 = new Block({ x: 2, y: 0, color: 'blue' }) + const b4 = new Block({ x: 2, y: 1, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + it('returns true when right L B tetromino exists', () => { + const b1 = new Block({ x: 0, y: 2, color: 'blue' }) + const b2 = new Block({ x: 1, y: 2, color: 'blue' }) + const b3 = new Block({ x: 1, y: 1, color: 'blue' }) + const b4 = new Block({ x: 1, y: 0, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + it('returns true when right L C tetromino exists', () => { + const b1 = new Block({ x: 0, y: 1, color: 'blue' }) + const b2 = new Block({ x: 0, y: 0, color: 'blue' }) + const b3 = new Block({ x: 1, y: 1, color: 'blue' }) + const b4 = new Block({ x: 2, y: 1, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + it('returns true when right L D tetromino exists', () => { + const b1 = new Block({ x: 0, y: 0, color: 'blue' }) + const b2 = new Block({ x: 1, y: 0, color: 'blue' }) + const b3 = new Block({ x: 0, y: 1, color: 'blue' }) + const b4 = new Block({ x: 0, y: 2, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + // 1 1 + // *1 1* ** ** + // ** ** * * + // A B C D + it('returns true when Z A tetromino exists', () => { + const b1 = new Block({ x: 0, y: 1, color: 'blue' }) + const b2 = new Block({ x: 0, y: 0, color: 'blue' }) + const b3 = new Block({ x: 1, y: 0, color: 'blue' }) + const b4 = new Block({ x: 1, y: 2, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + it('returns true when Z B tetromino exists', () => { + const b1 = new Block({ x: 0, y: 1, color: 'blue' }) + const b2 = new Block({ x: 1, y: 0, color: 'blue' }) + const b3 = new Block({ x: 1, y: 1, color: 'blue' }) + const b4 = new Block({ x: 0, y: 2, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + it('returns true when Z C tetromino exists', () => { + const b1 = new Block({ x: 0, y: 0, color: 'blue' }) + const b2 = new Block({ x: 1, y: 0, color: 'blue' }) + const b3 = new Block({ x: 1, y: 1, color: 'blue' }) + const b4 = new Block({ x: 2, y: 1, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + it('returns true when Z D tetromino exists', () => { + const b1 = new Block({ x: 0, y: 1, color: 'blue' }) + const b2 = new Block({ x: 1, y: 0, color: 'blue' }) + const b3 = new Block({ x: 1, y: 1, color: 'blue' }) + const b4 = new Block({ x: 2, y: 0, color: 'blue' }) + const blocks = [b1, b2, b3, b4] + const checker = new TetrominoChecker(blocks, 0) + expect(checker.check()).toEqual(true) + }) + + // 1 1 + // ** ** 1 *1* + // * * *** * + // A B C D + it('returns true when T A tetromino exists') + it('returns true when T B tetromino exists') + it('returns true when T C tetromino exists') + it('returns true when T D tetromino exists') +}) diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml new file mode 100644 index 0000000..858f601 --- /dev/null +++ b/spec/javascripts/support/jasmine.yml @@ -0,0 +1,50 @@ +# path to parent directory of src_files +# relative path from Rails.root +# defaults to app/assets/javascripts +src_dir: "app/assets/javascripts" + +# path to additional directory of source file that are not part of assets pipeline and need to be included +# relative path from Rails.root +# defaults to [] +# include_dir: +# - ../mobile_app/public/js + +# path to parent directory of css_files +# relative path from Rails.root +# defaults to app/assets/stylesheets +css_dir: "app/assets/stylesheets" + +# list of file expressions to include as source files +# relative path from src_dir +src_files: + - "application.{js.coffee,js,coffee}" + +# list of file expressions to include as css files +# relative path from css_dir +css_files: + +# path to parent directory of spec_files +# relative path from Rails.root +# +# Alternatively accept an array of directory to include external spec files +# spec_dir: +# - spec/javascripts +# - ../engine/spec/javascripts +# +# defaults to spec/javascripts +spec_dir: spec/javascripts + +# list of file expressions to include as helpers into spec runner +# relative path from spec_dir +helpers: + - "helpers/**/*.{js,es6.js}" + +# list of file expressions to include as specs into spec runner +# relative path from spec_dir +spec_files: + - "**/*[Ss]pec.{js.jsx,jsx,es6.jsx,es6.js,js}" + +# path to directory of temporary files +# (spec runner and asset cache) +# defaults to tmp/jasmine +tmp_dir: "tmp/jasmine"