diff --git a/brunch-config.js b/brunch-config.js index 914a00ac..f3d30bf4 100644 --- a/brunch-config.js +++ b/brunch-config.js @@ -49,9 +49,5 @@ exports.config = { "js/app.js": ["web/static/js/app"], "js/skin.js": ["web/static/js/skin"], } - }, - - npm: { - enabled: true, } }; diff --git a/lib/battle_snake/endpoint.ex b/lib/battle_snake/endpoint.ex index 15fd85b2..e1c22747 100644 --- a/lib/battle_snake/endpoint.ex +++ b/lib/battle_snake/endpoint.ex @@ -9,7 +9,7 @@ defmodule BattleSnake.Endpoint do # when deploying your static files in production. plug Plug.Static, at: "/", from: :battle_snake, gzip: false, - only: ~w(css fonts images js favicon.ico robots.txt) + only: ~w(css fonts audio images js favicon.ico robots.txt) # Code reloading can be explicitly enabled under the # :code_reloader configuration of your endpoint. diff --git a/package.json b/package.json index 77ee0039..e3624af3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "watch": "brunch watch --stdin" }, "dependencies": { - "jquery": "^3.1.0", + "howler": "^2.0.2", + "jquery": "^3.1.1", "mousetrap": "^1.6.0", "phoenix": "file:deps/phoenix", "phoenix_html": "file:deps/phoenix_html", diff --git a/web/controllers/skin_controller.ex b/web/controllers/skin_controller.ex index 7a7a316a..ffee6822 100644 --- a/web/controllers/skin_controller.ex +++ b/web/controllers/skin_controller.ex @@ -7,6 +7,7 @@ defmodule BattleSnake.SkinController do {:ok, game_form} = GameForm.get(id) conn |> put_layout(false) + |> put_resp_header("access-control-allow-origin", "*") |> render("show.html", game: game_form) end end diff --git a/web/static/assets/audio/Barrel-Exploding.mp3 b/web/static/assets/audio/Barrel-Exploding.mp3 new file mode 100644 index 00000000..ecb7226f Binary files /dev/null and b/web/static/assets/audio/Barrel-Exploding.mp3 differ diff --git a/web/static/assets/audio/Blast.mp3 b/web/static/assets/audio/Blast.mp3 new file mode 100644 index 00000000..cd0ec717 Binary files /dev/null and b/web/static/assets/audio/Blast.mp3 differ diff --git a/web/static/assets/audio/Bomb-3.mp3 b/web/static/assets/audio/Bomb-3.mp3 new file mode 100644 index 00000000..b2e6825b Binary files /dev/null and b/web/static/assets/audio/Bomb-3.mp3 differ diff --git a/web/static/assets/audio/Cartoon-Bite.mp3 b/web/static/assets/audio/Cartoon-Bite.mp3 new file mode 100644 index 00000000..b8585e7d Binary files /dev/null and b/web/static/assets/audio/Cartoon-Bite.mp3 differ diff --git a/web/static/assets/audio/Cartoon-Munch.mp3 b/web/static/assets/audio/Cartoon-Munch.mp3 new file mode 100644 index 00000000..da003e56 Binary files /dev/null and b/web/static/assets/audio/Cartoon-Munch.mp3 differ diff --git a/web/static/assets/audio/Explosion.mp3 b/web/static/assets/audio/Explosion.mp3 new file mode 100644 index 00000000..ffde4a2d Binary files /dev/null and b/web/static/assets/audio/Explosion.mp3 differ diff --git a/web/static/assets/audio/Grenade-Explosion.mp3 b/web/static/assets/audio/Grenade-Explosion.mp3 new file mode 100644 index 00000000..fc88359c Binary files /dev/null and b/web/static/assets/audio/Grenade-Explosion.mp3 differ diff --git a/web/static/assets/audio/Grenade.mp3 b/web/static/assets/audio/Grenade.mp3 new file mode 100644 index 00000000..0f79435d Binary files /dev/null and b/web/static/assets/audio/Grenade.mp3 differ diff --git a/web/static/assets/audio/Wilhelm_Scream.ogg b/web/static/assets/audio/Wilhelm_Scream.ogg new file mode 100644 index 00000000..27e401f8 Binary files /dev/null and b/web/static/assets/audio/Wilhelm_Scream.ogg differ diff --git a/web/static/assets/audio/playlists.json b/web/static/assets/audio/playlists.json new file mode 100644 index 00000000..8e3184bc --- /dev/null +++ b/web/static/assets/audio/playlists.json @@ -0,0 +1,55 @@ +{ + "effects": { + "eat": [ + { "url": "./Cartoon-Munch.mp3", "volume": 0.2 } + ,{ "url": "./Cartoon-Bite.mp3", "volume": 0.2 } + ] + ,"explode": [ + { "url": "./Barrel-Exploding.mp3" } + ,{ "url": "./Grenade.mp3" } + ,{ "url": "./Blast.mp3" } + ,{ "url": "./Bomb-3.mp3" } + ,{ "url": "./Explosion.mp3" } + ,{ "url": "./Grenade-Explosion.mp3" } + ,{ "url": "./Wilhelm_Scream.ogg" } + ] + } + ,"music": { + "prologue": [ + {"url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-ZkdYblAwMVpMMEU" + ,"name": "14 - Vena's Dance.mp3" } + ,{"url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-Nm8zR3d6TWZCUkk" + ,"name": "9-42 The Apple_ M4 tk 1.mp3" } + ,{"url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-bGUzSU8wZ19sMzQ" + ,"name": "9-39 The Apple_ M1 tk 1.mp3" } + ] + ,"fight": [ + { "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-UHpzSzNkOXBUMEU" + ,"name": "Speedy Reader" } + ,{ "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-VWdZT1BwZTU1WVk" + ,"name": "Temporal Wake"} + ,{ "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-ZFdHZEpDYzFDd2s" + ,"name": "Surprise Attack" } + ,{ "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-UUszZWRuZzZ3NGs" + ,"name": "Klingon Battle" } + ,{ "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-alhGWW1RMWJmSk0" + ,"name": "7-12 Amok Time_ Ritual _ To the Deat" } + ,{ "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-YVlfaTc2RkRPTGc" + ,"name": "Amok Time The Ancient Combat" } + ,{ "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-Nkd5RnB5OG5KY2s" + ,"name": "James Horner - 1986 - Aliens - Deluxe Edition - Ripley's Rescue" } + ,{ "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-dHJ3dk1HaFB1cVE" + ,"name": "Utah Saints - Utah Saints Take On The Theme From Mortal Kombat" } + ,{ "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-YlI4YWM3WHMzcXM" + ,"name": "Traci Lords - Control (Juno Reactor Instrumental)"} + ,{ "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-TkZtTmNQTmJoNXc" + ,"name": "The Immortals - Techno-Syndrome 7 Mix"} + ] + ,"epilogue": [ + { "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-c0FGQk1IR1JOS1k" + ,"name": "03 - Survivors.mp3"} + ,{ "url": "http://drive.google.com/uc?export=download&id=0B0Y-FESP0bc-MEd4VkxSS3ZMenc" + ,"name": "04 - Prime Specimen.mp3"} + ] + } +} diff --git a/web/static/css/_layout.scss b/web/static/css/_layout.scss index 69212d6c..9a949703 100644 --- a/web/static/css/_layout.scss +++ b/web/static/css/_layout.scss @@ -7,9 +7,3 @@ main { font-size: ms(-1); } } - -.view-btn { - position: absolute; - bottom: 10px; - left: 10px; -} diff --git a/web/static/js/skin.js b/web/static/js/skin.js index 4556d0fc..e32279e0 100644 --- a/web/static/js/skin.js +++ b/web/static/js/skin.js @@ -18,7 +18,6 @@ import "phoenix_html" // paths "./socket" or full ones "web/static/js/socket". import socket from "./socket" import $ from "jquery"; -import vue from "vue"; import Mousetrap from "mousetrap"; import GameRenderer from "./snake"; diff --git a/web/static/js/snake.js b/web/static/js/snake.js index f2070564..791fea2b 100644 --- a/web/static/js/snake.js +++ b/web/static/js/snake.js @@ -1,11 +1,119 @@ +/* Explodey Battlesnake skin + * Noel Burton-Krahn + */ import $ from "jquery"; -import Vue from "vue"; import * as THREE from "three"; import * as TWEEN from "tween.js"; +import { Howler, Howl } from 'howler'; +import Vue from 'vue/dist/vue.js'; var SNAKE_HEAD_URL = "/images/snake_head_nerdy.png"; var SNAKE_HEAD_SCALE = 1.5; var FOOD_URL = "/images/food.png" +var AUDIO_URL = "/audio/" +var AUDIO_PLAYLISTS_URL = AUDIO_URL + "playlists.json" + +function audio_url(url) { + if( url[0] == '.' ) { + url = AUDIO_URL + url; + } + return url; +} + +function audio_list(list, preload) { + var audios = []; + for(var i=0; i= list.length ) { + i = list.length; + } + return list[i]; +} + +var effects_playlist = {}; +var effects_volume = 0.5; +function set_effects_volume(val) { + effects_volume = val; +} +var effects_on = true; +function set_effects_on(val) { + effects_on = val; + $('#effects-on').prop('checked', effects_on); +} +function play_effects(name) { + if( effects_on && effects_playlist ) { + var player = choose(effects_playlist[name]); + var volume = effects_volume * (player.playlist.volume || 1); + player.player.play(); + player.player.volume(volume); + } +} + +var music_playlist = {}; +var music_volume = 0.2; +var music_theme; +var music_player; +var music_on = false; +function set_music_volume(val) { + music_volume = val; + if( music_player ) { + var volume = music_volume * (music_player.volume || 1); + music_player.player.volume(volume); + } +} +function set_music_on(val) { + music_on = val; + play_music(); +} +function play_music(theme, continue_theme) { + if( continue_theme && theme == music_theme ) { + return; + } + + if( theme ) { + music_theme = theme; + } + + if( music_playlist && music_theme ) { + var old_player = music_player; + if( old_player && old_player.player ) { + old_player.player.off('end'); + old_player.player.stop(); + } + if( music_on ) { + music_player = choose(music_playlist[music_theme]); + var volume = music_volume * (music_player.volume || 1); + if( !music_player.player ) { + music_player.player = new Howl({ + src: [audio_url(music_player.playlist.url)], + format: [music_player.playlist.format || "mp3"], + volume: volume, + onend: function() { + play_music(music_theme); + }, + loaderror: function(id, msg) { + console.error("play_music loaderror msg=" + msg); + play_music(music_theme); + } + }); + } + music_player.player.play(); + music_player.player.volume(volume); + } + } +} function SnakeInfoDiv(div_id, data) { // render snake info into a div using Vue @@ -100,7 +208,7 @@ function hex2rgba(hex, opacity) { function interpolateWithArcs(pts) { /* - return a function(t) that interpolates pts with straight lines and arcs, 0<=t<=1 + return a function(t) that interpolates pts with straight lines and arcs, 0<=t<=1 */ function interp_arc(pt0, pt1, pt2) { // return a function(t) that interpolates from the @@ -197,12 +305,40 @@ function memoize(fn) { }; } -var img_loader = new THREE.TextureLoader(); -var imgTexture = memoize(function(url) { - var tex = img_loader.load(url); +var texture_cache = {}; +var load_texture = function(url, callback) { + var tex = texture_cache[url]; + if( tex ) { + if( callback ) { + callback(tex); + } + return tex; + } + + var loader = new THREE.TextureLoader(); + loader.crossOrigin = ''; + var tex = loader.load(url + ,function(tex) { + tex.minFilter = THREE.LinearFilter; + texture_cache[url] = tex; + if( callback ) { + callback(tex); + } + } + ,function(xhr) {} + ,function(xhr) { + if( url != SNAKE_HEAD_URL ) { + tex = loader.load(SNAKE_HEAD_URL); + tex.minFilter = THREE.LinearFilter; + texture_cache[url] = tex; + if( callback ) { + callback(tex); + } + } + }); tex.minFilter = THREE.LinearFilter; return tex; -}); +}; var radialCanvas = memoize(function(width, height, stops) { var canvas = document.createElement('canvas'); @@ -265,8 +401,8 @@ function SnakeRenderer(board_renderer) { self.render = function(snake) { //console.time("SnakeRenderer.render"); - var color = snake.color || stringToColor(snake.name); - var body = snake.coords || snake.body; + var color = snake.color; + var body = snake.body; // body if( !body_material ) { @@ -313,12 +449,12 @@ function SnakeRenderer(board_renderer) { } // head - var img = snake.img || SNAKE_HEAD_URL; + var img = snake.img; if( !head_material ) { // TODO - reload material if head changes head_material = new THREE.SpriteMaterial({ - map: imgTexture(img) }); + load_texture(img, function(tex) {head_material.map = tex}); } if( !head_particle ) { head_particle = new THREE.Sprite(head_material); @@ -335,6 +471,8 @@ function SnakeRenderer(board_renderer) { var dur = 750; var mag = 5; + play_effects("explode"); + // explode particles var explode_material = explodeMaterial(); new TWEEN.Tween(explode_material) @@ -430,14 +568,15 @@ function SnakeRenderer(board_renderer) { function FoodRenderer(board_renderer, food_pt) { var self = this; - var material = new THREE.SpriteMaterial({ - map: imgTexture(FOOD_URL) - }); + var material = new THREE.SpriteMaterial(); + load_texture(FOOD_URL, function(tex) { material.map = tex; }); var sprite = new THREE.Sprite(material); board_renderer.board_node.add(sprite); sprite.position.copy(food_pt); self.explode = function() { + play_effects("eat"); + var explode_material = foodMaterial(); var mag = 5; var dur = 750; @@ -526,6 +665,27 @@ function SnakeBoardRenderer(game_renderer) { var grid_mesh = null; var board_info_vue = null; + self.fixup_snake = function(snake) { + if( !('board_id' in snake) ) { + snake.board_id = snake.id; + } + if( !('img' in snake) ) { + snake.img = snake.head_url || SNAKE_HEAD_URL; + } + if( !('color' in snake) ) { + snake.color = stringToColor(snake.board_id); + } + if( !('body' in snake) ) { + snake.body = snake.coords; + } + } + + self.fixup_snakes = function(snakes) { + for(var i=0; i - - + + - Skin! + BattleSnake 2017 - Explodey skin "> @@ -16,18 +16,33 @@ window.BattleSnake = <%= raw battle_snake_js_object(assigns) %>; - - - -
-
- -