はてなハイク STAR FIGHTERS

    @@ -6,10 +6,8 @@ * @require */ - (function(){ - if(! 'forEach' in Array.prototype){ Array.prototype.forEach = function(cb){ for(var i=0,l=this.length;i<l;i++){ @@ -28,6 +26,7 @@ }; } + //////////////////////////////////////////////////////////// var defclass = function(spec, initializer){ var base = spec.base; @@ -65,6 +64,152 @@ return R; }; + //////////////////////////////////////////////////////////// + + var AbstractKeyManager = defclass( + { + name: 'AbstractKeyManager', + base: Object + }, + function(SUPER, Class){ + this.initialize = function(){ + this._hooks = []; + this._lastKey = null; + this._lastEvent = null; + }; + + this.addHook = function(hook){ + this._hooks.push(hook); + }; + + this.deleteHook = function(hook){ + var hooks = this._hooks; + var found = null; + for(var i=0,l=hooks.length;i<l;i++){ + if(hook === hooks[i]){ + found = i; + break; + } + } + if( found !== null ){ + hooks.splice( found, 1 ); + } + }; + + this._runHooks = function(){ + var self = this; + this._hooks.forEach( + function(hook){ + hook.apply(self, []); + } + ); + }; + + this.pushKey = function(k){ + this._lastKey = k; + this._lastEvent = 'push'; + }; + + this.releaseKey = function(k){ + this._lastKey = k; + this._lastEvent = 'release'; + }; + } + ); + + + var ShotKeyManager = defclass( + { + name: 'ShotKeyManager', + base: AbstractKeyManager + }, + function(SUPER, Class){ + this.releaseKey = function(k){ + SUPER.releaseKey.apply(this, [k]); + this._runHooks(); + }; + } + ); + + var ArrowKeyManager = defclass( + { + name: 'ArrowKeyManager', + base: AbstractKeyManager + }, + function(SUPER, Class){ + + this.initialize = function(){ + SUPER.initialize.apply(this,[]); + this.pressed = {n:0,s:0,w:0,e:0}; + }; + + this.pushKey = function(k){ + var pressed = this.pressed; + switch(k){ + case 'n': + pressed.n = (pressed.s > 0) ? 2 : 1; + break; + case 's': + pressed.s = (pressed.n > 0) ? 2 : 1; + break; + case 'w': + pressed.w = (pressed.e > 0) ? 2 : 1; + break; + case 'e': + pressed.e = (pressed.w > 0) ? 2 : 1; + break; + } + this._runHooks(); + }; + + this.releaseKey = ( + function(){ + var timer = null; + return function(k){ + var pressed = this.pressed; + switch(k){ + case 'n': + pressed.n = 0; + break; + case 's': + pressed.s = 0; + break; + case 'w': + pressed.w = 0; + break; + case 'e': + pressed.e = 0; + } + var self = this; + if(timer){ + clearTimeout(timer); + timer = null; + } + timer = setTimeout( + function(){ + self._runHooks(); + timer = null; + }, + 50 + ); + }; + } + )(); + + this.getCurrentDirection = function(){ + var pressed = this.pressed; + var x = 0; + var y = 0; + if(pressed.s != pressed.n) y = pressed.s > pressed.n ? 1 : -1; + if(pressed.w != pressed.e) x = pressed.e > pressed.w ? 1 : -1; + return {x:x, y:y}; + }; + + } + ); + + //////////////////////////////////////////////////////////// + var Game = new ( defclass( { @@ -76,6 +221,72 @@ this.initialize = function(){ this.allElems = {}; this.elemDic = {}; + + var akman = new ArrowKeyManager(); + var skman = new ShotKeyManager(); + document.addEventListener( + 'keypress', + function(e){ e.preventDefault();}, + false + ); + + window.addEventListener( + 'keydown', function(e){ + e.stopPropagation(); + e.preventDefault(); + // console.log(e.keyCode); + switch(e.keyCode){ + case 37: //LEFT + akman.pushKey('w'); + break; + case 38: //UP + akman.pushKey('n'); + break; + case 39: //RIGHT + akman.pushKey('e'); + break; + case 40: //DOWN + akman.pushKey('s'); + break; + case 90: + skman.pushKey(1); + } + }, true + ); + + window.addEventListener( + 'keyup', function(e){ + e.stopPropagation(); + e.preventDefault(); + switch(e.keyCode){ + case 37: //LEFT + akman.releaseKey('w'); + break; + case 38: //UP + akman.releaseKey('n'); + break; + case 39: //RIGHT + akman.releaseKey('e'); + break; + case 40: //DOWN + akman.releaseKey('s'); + break; + case 90: // Z + skman.releaseKey(1); + } + }, true + ); + + this.arrowKeyManager = akman; + this.shotKeyManager = skman; + }; + + this.getElementsByClass = function(cls){ + var elemDic = this.elemDic; + var slot = elemDic[cls.name || ''] || {}; + var R = []; + for(var f in slot) R.push(slot[f]); + return R; }; this.registerElement = function(elem){ @@ -118,11 +329,12 @@ if(l < 1) continue; var chslot = (checked[f] || (checked[f] = {})); for(i=0,l=tclasses.length;i<l;i++){ +//debugger; var edslot = elemDic[tclasses[i]]; for(ff in edslot){ if(chslot[ff]) continue; chslot[ff] = true; - var target = edslot[f]; + var target = edslot[ff]; if(!elem.checkHit(target)) continue; (checked[target.key] || (checked[target.key] = {}))[f] = true; @@ -148,114 +360,164 @@ ); // end of new - var GameElement = defclass( - { - base: Object, - name: 'GameElement' - }, - function(SUPER, Class){ - var counter = 0; - - this.initialize = function(x,y){ - this.key = 'elem_'+ counter++; - this._x = x; - this._y = y; - this._pre_x = NaN; - this._pre_y = NaN; - this._speed = 1; - this._dx = 0; - this._dy = 0; - this._pdx = 0; - this._pdy = -1; - this._img = null; - Game.registerElement(this); - }; + //////////////////////////////////////////////////////////// - this.initImage = function(url){ - var img = document.createElement('img'); - document.body.appendChild(img); - img.style.position = 'fixed'; - img.style.zIndex = '65536'; - img.src = url; - this._img = img; - return img; - }; + var genImage = function(url , container){ + var img = document.createElement('img'); + (container || document.body).appendChild(img); + img.src = url; + return img; + }; - this.setSpeed = function(s){ - this._speed = s; - }; + var GameElement = defclass( + { + base: Object, + name: 'GameElement' + }, + function(SUPER, Class){ + var counter = 0; + + this.initialize = function(x,y){ + this.key = 'elem_'+ counter++; + this._x = x; + this._y = y; + this._pre_x = NaN; + this._pre_y = NaN; + this._speed = 1; + this._dx = 0; + this._dy = 0; + this._pdx = 0; + this._pdy = -1; + this._elem = this.makeElem(); + this.initElem(this._elem); + Game.registerElement(this); + this.redraw(); + }; - this.getPosition = function(){ - return { - x: this._x , - y: this._y - }; - }; + this.makeElem = function(){ + var div = document.createElement('div'); + div.style.border = 'solid black 2px'; + return div; + }; - this.getSpeed = function(){ - return this._speed; - }; + this.initElem = function(elem){ + document.body.appendChild(elem); + elem.style.zIndex = 65536; + elem.style.position = 'fixed'; + }; - this.setDirection = function(x, y){ - this._dx = x; - this._dy = y; - }; + this.setSpeed = function(s){ + this._speed = s; + }; - this.getDirection = function(){ - return { x:this._dx, y:this._dy }; - }; + this.getPosition = function(){ + return { + x: this._x , + y: this._y + }; + }; + + this.getSpeed = function(){ + return this._speed; + }; + + this.setDirection = function(x, y){ + this._dx = x; + this._dy = y; + }; - this.setShotDirection = function(x,y){ - this._pdx = x; - this._pdy = y; - }; + this.getDirection = function(){ + return { x:this._dx, y:this._dy }; + }; - this.getShotDirection = function(){ - return { x:this._pdx, y:this._pdy }; - }; + this.setShotDirection = function(x,y){ + this._pdx = x; + this._pdy = y; + }; + + this.getShotDirection = function(){ + return { x:this._pdx, y:this._pdy }; + }; - // ステップごとの自己状態の変更 - this.step = function(){ - this._pre_x = this._x; - this._pre_y = this._y; - this._x = this._x + this._dx * this._speed; - this._y = this._y + this._dy * this._speed; - }; + // ステップごとの自己状態の変更 + this.step = function(){ + this._pre_x = this._x; + this._pre_y = this._y; + this._x = this._x + this._dx * this._speed; + this._y = this._y + this._dy * this._speed; + }; - // - this.dispose = function(){ - var img = this._img; - img.parentNode.removeChild(img); - }; + // + this.dispose = function(){ + this.disposeElements(); + Game.unregisterElement(this); + for(var f in this){ + if('function' == typeof this[f]){ + this[f] = function(){}; + } + } + }; - // フレームごとの計算結果を元に - this.redraw = function(){ - var img = this._img; - if(this._dispose){ - Game.unregisterElement(this); - this.dispose(); - return; - } - var sx = this._x; - var sy = this._y; - img.style.top = (sy - img.clientHeight / 2) + 'px'; - img.style.left = (sx - img.clientWidth / 2) + 'px'; - }; + this.disposeElements = function(){ + var img = this._elem; + if(img.parentNode) img.parentNode.removeChild(img); + }; + + // フレームごとの計算結果を元に + this.redraw = function(){ + var img = this._elem; + if(this._dispose){ + this.dispose(); + return; + } + var sx = this._x; + var sy = this._y; + img.style.top = (sy - img.clientHeight / 2) + 'px'; + img.style.left = (sx - img.clientWidth / 2) + 'px'; + }; - // 当たり判定の対象となるクラス群 - this.getTargetClasses = function(){ - return []; - }; + // 当たり判定の対象となるクラス群 + this.getTargetClasses = function(){ + return []; + }; + + // 自身のフレームごとの軌跡 + this.getTrack = function(){ + var R = 0; + return R; + }; - // 自身のフレームごとの軌跡 - this.getTrack = function(){ - var R = 0; - return R; - }; + // 当たり判定 + this.getHitRectangle = function(){ + var img = this._elem; + var w = img.clientWidth; + var h = img.clientHeight; + var sx = this._x; + var sy = this._y; + var x0 = sx - w / 2; + var y0 = sy - h / 2; + return { + x1: x0, + y1: y0, + x2: x0 + w, + y2: y0 + h + }; + }; - // elem との当り判定を書く + // elem との当り判定を書く + // TODO すりぬけない方法について学習する this.checkHit = function(elem){ - return false; + var sr = this.getHitRectangle(); + var er = elem.getHitRectangle(); + var x0 = sr.x1; + var x1 = sr.x2; + var x2 = er.x1; + var x3 = er.x2; + var y0 = sr.y1; + var y1 = sr.y2; + var y2 = er.y1; + var y3 = er.y2; + return (x0 < x3 && x2 < x1 && y0 < y3 && y2 < y1); }; // elem 衝突時の振舞い @@ -265,160 +527,358 @@ this.onScreen = function(){ var sx = this._x; var sy = this._y; - return (sx > 0 && sx < window.innerWidth && - sy > 0 && sy < window.innerHeight); + return (sx >= 0 && sx < window.innerWidth && + sy >= 0 && sy < window.innerHeight); }; - } - ); - var Bullet = defclass( - { - base: GameElement, - name: 'Bullet' - }, - function(SUPER, Class){ - this.initialize = function(x,y){ - SUPER.initialize.apply(this,[x, y]); - this.setDirection(0,-1); - this.setSpeed(10); - this.initImage('http://s.hatena.ne.jp/images/star.gif'); - }; - this.step = function(){ - SUPER.step.apply(this, []); - if(!this.onScreen()) this._dispose = true; + this.backToScreen = function(){ + var sx = this._x; + var sy = this._y; + if(sx < 0){ + this._x = 0; + } else if (sx >= window.innerWidth){ + this._x = window.innerWidth - 1; + } + if(sy < 0){ + this._y = 0; + } else if (sy >= window.innerHeight){ + this.y = window.innerHeight - 1; + } }; + } ); - var User = defclass( - { - base: GameElement, - name: 'User' - }, function(SUPER,Class){ - - this.initialize = function(name){ - - SUPER.initialize.apply(this,[0,0]); - this.name = name; - var img = this.initImage( - 'http://www.st-hatena.com/users/' + name.substring(0,2) + - '/' + name + '/profile.gif' - ); - this._defaultSpeed = 5; - this.setDirection(0, -1); - this.setSpeed(0); - this._x = - Math.floor((window.innerWidth - img.clientWidth ) / 2 - ); - this._y = - (window.innerHeight - img.clientHeight - 10); - }; + var Explode = defclass( + { + base: GameElement, + name: 'Explode' + }, - this.getHotSpot = function(){ - var img = this._img; - return { - x: Math.floor(this._x + img.clientWidth / 2), - y: Math.floor(this._y + img.clientHeight / 2) - }; - }; + function(SUPER, Class){ + this.initialize = function(x, y){ + SUPER.initialize.apply(this, [x, y]); + this._frame = 0; + this._stars = null; + }; - this.shot = function(){ - var pos = this.getPosition(); - var bullet = new Bullet(pos.x, pos.y); - var dir = this.getDirection(); - bullet.setDirection(dir.x, dir.y); - bullet.setSpeed(this._defaultSpeed + 2); - }; + this.makeElem = function(){ + var div = document.createElement('div'); + div.style.width = '100px'; + div.style.height = '100px'; + var img = genImage('http://s.hatena.ne.jp/images/star-red.gif', div); + img.style.position = 'absolute'; + var imgs = [ img ]; + this.stars = imgs; + for(var i =0; i < 15; i++){ + var clone = img.cloneNode(true); + imgs.push( clone ); + div.appendChild( clone ); + } + return div; + }; - this.setDirection = function(x, y){ - if(x == 0 && y == 0){ - this.setSpeed(0); - } else { - this.setSpeed(this._defaultSpeed); - SUPER.setDirection.apply(this, [x,y]); - } - }; + this.step = function(){ + this._frame++; + if(this._frame > 40){ + this.dispose(); + } + }; + + this.redraw = function(){ + SUPER.redraw.apply(this, []); + var imgs = this.stars; + var frame = this._frame; + var sx = this._x; + var sy = this._y; + for(var i = 0;i<16;i++){ + var rad = i / 16 * 2 * Math.PI; + var x = (Math.cos(rad)) * frame; + var y = (Math.sin(rad)) * frame;; + imgs[i].style.top = (50 + y) + 'px'; + imgs[i].style.left = (50 + x) + 'px'; + } + }; + } + + ); + + + + //////////////////////////////////////////////////////////// + var Bullet = defclass( + { + base: GameElement, + name: 'Bullet' + }, + function(SUPER, Class){ + this.initialize = function(x,y){ + SUPER.initialize.apply(this,[x, y]); + this.setDirection(0,-1); + this.setSpeed(10); + }; + + + this.step = function(){ + SUPER.step.apply(this, []); + if(!this.onScreen()) this._dispose = true; + }; + } + ); + + var EBullet = defclass( + { + base: Bullet, + name: 'EBullet' + }, + function(SUPER, Class){ + this.makeElem = function(){ + return genImage('http://s.hatena.ne.jp/images/star.gif'); + }; + } + ); + + var PBullet = defclass( + { + base: Bullet, + name: 'PBullet' + }, + function(SUPER, Class){ + this.makeElem = function(){ + return genImage('http://s.hatena.ne.jp/images/star-green.gif'); + }; + } + ); + + //////////////////////////////////////////////////////////// + var Motion; + var RandomMotion; + + var User = defclass( + { + base: GameElement, + name: 'User' + }, function(SUPER,Class){ + + this.initialize = function(name){ + this.name = name; + SUPER.initialize.apply(this,[0,0]); + this._defaultSpeed = 5; + this.setDirection(0, -1); + this.setSpeed(0); + }; + + this.makeElem = function(){ + var name = this.name; + return genImage( + 'http://www.st-hatena.com/users/' + name.substring(0,2) + + '/' + name + '/profile.gif' + ); + }; + + this.explode = function(){ + new Explode(this._x, this._y); + this.dispose(); + }; - } - ); + } + ); - var ArrowKeyManager = defclass( + var Enemy = defclass( { - name: 'ArrowKeyManager', - base: Object + base: User, + name: 'Enemy' }, function(SUPER, Class){ + this.initialize = function(name){ + SUPER.initialize.apply(this,[name]); + var img = this._elem; + this._x = Math.floor((window.innerWidth - img.clientWidth ) / 2 + ); + this._y = img.clientHeight; + this._motion = new RandomMotion( this ); + }; - this.initialize = function(hook){ - this.pressed = {n:0,s:0,w:0,e:0}; - this._hook = hook; + this.setMotion = function(motion){ + this._motion = motion; }; - this.pushKey = function(k){ - var pressed = this.pressed; - switch(k){ - case 'n': - pressed.n = (pressed.s > 0) ? 2 : 1; - break; - case 's': - pressed.s = (pressed.n > 0) ? 2 : 1; - break; - case 'w': - pressed.w = (pressed.e > 0) ? 2 : 1; - break; - case 'e': - pressed.e = (pressed.w > 0) ? 2 : 1; - break; + this.step = function(){ + this._motion.step(); + SUPER.step.apply(this, []); + if(!this.onScreen()){ + this.backToScreen(); } - this._hook(this); }; - this.releaseKey = ( - function(){ - var timer = null; - return function(k){ - var pressed = this.pressed; - switch(k){ - case 'n': - pressed.n = 0; - break; - case 's': - pressed.s = 0; - break; - case 'w': - pressed.w = 0; - break; - case 'e': - pressed.e = 0; + this.getTargetClasses = function(){ + return ['PBullet']; + }; + + this.onHit = function(elem){ + if(elem instanceof PBullet){ + this.explode(); + } + }; + } + ); + + var Player = defclass( + { + base: User, + name: 'Player' + }, function(SUPER, Class){ + + this.initialize = function(name){ + SUPER.initialize.apply(this,[name]); + var self = this; + var img = this._elem; + this._arrowHook = function(){ + var dir = this.getCurrentDirection(); + self.setDirection(dir.x, dir.y); + }; + Game.arrowKeyManager.addHook( this._arrowHook ); + this._shotHook = function(){ self.shot(); }; + Game.shotKeyManager.addHook( this._shotHook ); + this._x = + Math.floor((window.innerWidth - img.clientWidth ) / 2 + ); + this._y = + (window.innerHeight - img.clientHeight - 10); + }; + + this.dispose = function(){ + Game.arrowKeyManager.deleteHook( this._arrowHook ); + Game.shotKeyManager.deleteHook( this._shothook ); + SUPER.dispose.apply(this, []); + }; + + this.getTargetClasses = function(){ + return ['Enemy', 'EBullet']; + }; + + this.onHit = function(elem){ + if(elem instanceof Enemy){ + this.explode(); + elem.explode(); + } + if(elem instanceof EBullet){ + this.explode(); + } + }; + + this.shot = function(){ + var pos = this.getPosition(); + var bullet = new PBullet(pos.x, pos.y); + var dir = this.getDirection(); + bullet.setDirection(dir.x, dir.y); + bullet.setSpeed(this._defaultSpeed + 2); + }; + + this.setDirection = function(x, y){ + if(x == 0 && y == 0){ + this.setSpeed(0); + } else { + this.setSpeed(this._defaultSpeed); + SUPER.setDirection.apply(this, [x,y]); + } + }; + } + ); + + //////////////////////////////////////////////////////////// + + Motion = defclass( + { + base: Object, + name: 'Motion' + }, + function(SUPER, Class){ + this.initialize = function(elem){ + this.elem = elem; + }; + this.step = function(){ + }; + } + ); + + RandomMotion = defclass( + { + base: Motion, + name: 'RandomMotion' + }, + function(SUPER, Class){ + this.initialize = function(elem){ + SUPER.initialize.apply(this, [elem]); + this._turn_at = null; + this._iter = 0; + this._changeDir(); + }; + + this._changeDir = function(){ + this._turn_at = 50; + var elem = this.elem; + var player = null; + if(this._iter++ > 3 && + Math.random() * 3 < 1){ + player = (Game.getElementsByClass(Player) || [])[0] || null; + } + this._iter++; + if(player){ + var dx = player._x - elem._x; + var dy = player._y - elem._y; + var ddx, ddy; + if(Math.abs(dx) > Math.abs(dy)){ + ddx = dx < 0 ? -1 : 1; + if(dy == 0){ + ddy = 0; + } else { + ddy = dy / Math.abs(dx); } - var self = this; - if(timer){ - clearTimeout(timer); - timer = null; + }else{ + ddy = dy < 0 ? -1 : 1; + if(dx == 0){ + ddx = 0; + } else{ + ddx = dx / Math.abs(dy); } - timer = setTimeout( - function(){ - self._hook(self); - timer = null; - }, - 50 - ); - }; + } + elem.setDirection(ddx, ddy); + } else { + elem.setDirection(Math.random() * 2 - 1, + Math.random() * 2 - 1); } - )(); + elem.setSpeed(Math.random() * 10); + }; - this.getCurrentDirection = function(){ - var pressed = this.pressed; - var x = 0; - var y = 0; - if(pressed.s != pressed.n) y = pressed.s > pressed.n ? 1 : -1; - if(pressed.w != pressed.e) x = pressed.e > pressed.w ? 1 : -1; - return {x:x, y:y}; + this.step = function(){ + switch(this._turn_at){ + case 40: + case 30: + case 20: + case 10: + case 0: + this.shot(); + } + if(this._turn_at-- < 0){ + this._changeDir(); + } + }; + + + this.shot = function(){ + var pos = this.elem.getPosition(); + var bullet = new EBullet(pos.x, pos.y); + var dir = this.elem.getDirection(); + bullet.setDirection(dir.x, dir.y); + bullet.setSpeed(this.elem.getSpeed() + 5); }; } ); + + //////////////////////////////////////////////////////////// var name = document.evaluate( './/p[@class="username"]/a/text()', @@ -426,64 +886,9 @@ null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0).data; - - var user = new User(name); - var akman = new ArrowKeyManager( - function(){ - var dir = this.getCurrentDirection(); - user.setDirection(dir.x, dir.y); - } - ); - - document.addEventListener( 'keypress', - function(e){ e.preventDefault();}, false); - - window.addEventListener( - 'keydown', function(e){ - e.stopPropagation(); - e.preventDefault(); - // console.log(e.keyCode); - switch(e.keyCode){ - case 37: //LEFT - akman.pushKey('w'); - break; - case 38: //UP - akman.pushKey('n'); - break; - case 39: //RIGHT - akman.pushKey('e'); - break; - case 40: //DOWN - akman.pushKey('s'); - break; - } - }, true - ); - - window.addEventListener( - 'keyup', function(e){ - e.stopPropagation(); - e.preventDefault(); - switch(e.keyCode){ - case 37: //LEFT - akman.releaseKey('w'); - break; - case 38: //UP - akman.releaseKey('n'); - break; - case 39: //RIGHT - akman.releaseKey('e'); - break; - case 40: //DOWN - akman.releaseKey('s'); - break; - case 90: // Z - user.shot(); - } - }, true - ); - + var user = new Player(name); + var enemy = new Enemy('default'); Game.start(); })();
  • /*
     * @title haiku-shoot
     * @description はてなハイクで星を撃つ
     * @include http://h.hatena.ne.jp/*
     * @license MIT License
     * @require 
     */
    
    (function(){
    
       if(! 'forEach' in Array.prototype){
         Array.prototype.forEach = function(cb){
           for(var i=0,l=this.length;i<l;i++){
             cb(this[i],i);
           }
         };
       }
    
       if(! 'map' in Array.prototype){
         Array.prototype.map = function(cb){
           var R = [];
           for(var i=0,l=this.length;i<l;i++){
             R.push(cb(this[i],i));
           }
           return R;
         };
       }
    
       ////////////////////////////////////////////////////////////
    
       var defclass = function(spec, initializer){
         var base = spec.base;
         var klass = function(){};
         klass.prototype = base.prototype;
         var proto = new klass;
         if(!('initialize' in proto)) proto.initialize = function(){};
         var R     = function(){
           this.constructor = arguments.callee;
           this.initialize.apply(this,arguments);
         };
    
         R.prototype = proto;
         R.name      = spec.name;
         R.base      = base;
    
         R.getBaseClasses = function(){
           var R = [this];
           if('getBaseClasses' in base){
             R.push.apply(R, base.getBaseClasses());
           } else {
             R.push(base);
           }
           return R;
         };
    
         initializer.apply(
           proto,
           [
             base.prototype,
             R
           ]
         );
    
         return R;
       };
    
       ////////////////////////////////////////////////////////////
    
       var AbstractKeyManager = defclass(
         {
           name: 'AbstractKeyManager',
           base: Object
         },
         function(SUPER, Class){
           this.initialize = function(){
             this._hooks     = [];
             this._lastKey   = null;
             this._lastEvent = null;
           };
    
           this.addHook = function(hook){
             this._hooks.push(hook);
           };
    
           this.deleteHook = function(hook){
             var hooks = this._hooks;
             var found = null;
             for(var i=0,l=hooks.length;i<l;i++){
               if(hook === hooks[i]){
                 found = i;
                 break;
               }
             }
             if( found !== null ){
               hooks.splice( found, 1 );
             }
           };
    
           this._runHooks = function(){
             var self = this;
             this._hooks.forEach(
               function(hook){
                 hook.apply(self, []);
               }
             );
           };
    
           this.pushKey = function(k){
             this._lastKey = k;
             this._lastEvent = 'push';
           };
    
           this.releaseKey = function(k){
             this._lastKey = k;
             this._lastEvent = 'release';
           };
         }
       );
    
    
       var ShotKeyManager = defclass(
         {
           name: 'ShotKeyManager',
           base: AbstractKeyManager
         },
         function(SUPER, Class){
           this.releaseKey = function(k){
             SUPER.releaseKey.apply(this, [k]);
             this._runHooks();
           };
         }
       );
    
       var ArrowKeyManager = defclass(
         {
           name: 'ArrowKeyManager',
           base: AbstractKeyManager
         },
         function(SUPER, Class){
    
           this.initialize = function(){
             SUPER.initialize.apply(this,[]);
             this.pressed = {n:0,s:0,w:0,e:0};
           };
    
           this.pushKey = function(k){
             var pressed = this.pressed;
             switch(k){
             case 'n':
               pressed.n = (pressed.s > 0) ? 2 : 1;
               break;
             case 's':
               pressed.s = (pressed.n > 0) ? 2 : 1;
               break;
             case 'w':
               pressed.w = (pressed.e > 0) ? 2 : 1;
               break;
             case 'e':
               pressed.e = (pressed.w > 0) ? 2 : 1;
               break;
             }
             this._runHooks();
           };
    
           this.releaseKey = (
             function(){
               var timer = null;
               return function(k){
                 var pressed = this.pressed;
                 switch(k){
                 case 'n':
                   pressed.n = 0;
                   break;
                 case 's':
                   pressed.s = 0;
                   break;
                 case 'w':
                   pressed.w = 0;
                   break;
                 case 'e':
                   pressed.e = 0;
                 }
                 var self = this;
                 if(timer){
                   clearTimeout(timer);
                   timer = null;
                 }
                 timer = setTimeout(
                   function(){
                     self._runHooks();
                     timer = null;
                   },
                   50
                 );
               };
             }
           )();
    
           this.getCurrentDirection = function(){
             var pressed = this.pressed;
             var x = 0;
             var y = 0;
             if(pressed.s != pressed.n) y = pressed.s > pressed.n ? 1 : -1;
             if(pressed.w != pressed.e) x = pressed.e > pressed.w ? 1 : -1;
             return {x:x, y:y};
           };
    
         }
       );
    
       ////////////////////////////////////////////////////////////
    
       var Game = new (
         defclass(
           {
             base: Object,
             name: 'Game'
           },
           function(SUPER, Class){
    
             this.initialize = function(){
               this.allElems = {};
               this.elemDic  = {};
               
               var akman = new ArrowKeyManager();
               var skman  = new ShotKeyManager();
               document.addEventListener(
                 'keypress',
                 function(e){ e.preventDefault();},
                 false
               );
               
               window.addEventListener(
                 'keydown', function(e){
                   e.stopPropagation();
                   e.preventDefault();
                   //       console.log(e.keyCode);
                   switch(e.keyCode){
                   case 37: //LEFT
                     akman.pushKey('w');
                     break;
                   case 38: //UP
                     akman.pushKey('n');
                     break;
                   case 39: //RIGHT
                     akman.pushKey('e');
                     break;
                   case 40: //DOWN
                     akman.pushKey('s');
                     break;
                   case 90:
                     skman.pushKey(1);
                   }
                 }, true
               );
       
               window.addEventListener(
                 'keyup', function(e){
                   e.stopPropagation();
                   e.preventDefault();
                   switch(e.keyCode){
                   case 37: //LEFT
                     akman.releaseKey('w');
                     break;
                   case 38: //UP
                     akman.releaseKey('n');
                     break;
                   case 39: //RIGHT
                     akman.releaseKey('e');
                     break;
                   case 40: //DOWN
                     akman.releaseKey('s');
                     break;
                   case 90: // Z
                     skman.releaseKey(1);
                   }
                 }, true
               );
       
               this.arrowKeyManager = akman;
               this.shotKeyManager  = skman;
             };
    
             this.getElementsByClass  = function(cls){
               var elemDic = this.elemDic;
               var slot    = elemDic[cls.name || ''] || {};
               var R = [];
               for(var f in slot) R.push(slot[f]);
               return R;
             };
    
             this.registerElement = function(elem){
               var self = this;
               var elemDic = this.elemDic;
               this.allElems[elem.key] = elem;
               elem.constructor.getBaseClasses().forEach(
                 function(cl){
                   if(!cl.name) return;
                   (elemDic[cl.name] || (elemDic[cl.name] = {}))[elem.key] = elem;
                 }
               );
             };
    
             this.unregisterElement = function(elem){
               delete this.allElems[elem.key];
               var elemDic = this.elemDic;
               elem.constructor.getBaseClasses().forEach(
                 function(cl){
                   if(!(cl.name && elemDic[cl.name])) return;
                   delete elemDic[cl.name][elem.key];
                 }
               );
             };
    
             this.iter = function(){
               var allElems = this.allElems;
               var elemDic  = this.elemDic;
               var i,l, ff;
               var elem;
               for(f in allElems){
                 allElems[f].step();
               }
    
               var checked = {};
               for(f in allElems){
                 elem = allElems[f];
                 var tclasses = elem.getTargetClasses();
                 l = tclasses.length;
                 if(l < 1) continue;
                 var chslot = (checked[f] || (checked[f] = {}));
                 for(i=0,l=tclasses.length;i<l;i++){
    //debugger;
                   var edslot = elemDic[tclasses[i]];
                   for(ff in edslot){
                     if(chslot[ff]) continue;
                     chslot[ff] = true;
                     var target = edslot[ff];
                     if(!elem.checkHit(target)) continue;
                     (checked[target.key] ||
                      (checked[target.key] = {}))[f] = true;
                     elem.onHit(target);
                     target.onHit(elem);
                   }
                 }
               }
    
               for(f in allElems){
                 allElems[f].redraw();
               }
             };
    
             this.start = function(){
               var self = this;
               (function(){
                  self.iter();
                  setTimeout(arguments.callee, 10);
                })();
             };
           }) // end of defclass
       ); // end of new
    
    
       ////////////////////////////////////////////////////////////
    
       var genImage = function(url , container){
         var img = document.createElement('img');
         (container || document.body).appendChild(img);
         img.src = url;
         return img;
       };
    
       var GameElement = defclass(
         {
           base: Object,
           name: 'GameElement'
         },
         function(SUPER, Class){
           var counter = 0;
           
           this.initialize = function(x,y){
             this.key    = 'elem_'+ counter++;
             this._x     = x;
             this._y     = y;
             this._pre_x = NaN;
             this._pre_y = NaN;
             this._speed = 1;
             this._dx    = 0;
             this._dy    = 0;
             this._pdx   = 0;
             this._pdy   = -1;
             this._elem  = this.makeElem();
             this.initElem(this._elem);
             Game.registerElement(this);
             this.redraw();
           };
    
           this.makeElem = function(){
             var div = document.createElement('div');
             div.style.border = 'solid black 2px';
             return div;
           };
    
           this.initElem = function(elem){
             document.body.appendChild(elem);
             elem.style.zIndex   = 65536;
             elem.style.position = 'fixed';
           };
    
           this.setSpeed = function(s){
             this._speed = s;
           };
    
           this.getPosition  = function(){
             return {
               x: this._x ,
               y: this._y
             };
           };
           
           this.getSpeed = function(){
             return this._speed;
           };
           
           this.setDirection = function(x, y){
             this._dx = x;
             this._dy = y;
           };
    
           this.getDirection = function(){
             return { x:this._dx, y:this._dy };
           };
    
           this.setShotDirection = function(x,y){
             this._pdx = x;
             this._pdy = y;
           };
           
           this.getShotDirection = function(){
             return { x:this._pdx, y:this._pdy };
           };
    
           // ステップごとの自己状態の変更
           this.step = function(){
             this._pre_x = this._x;
             this._pre_y = this._y;
             this._x = this._x + this._dx * this._speed;
             this._y = this._y + this._dy * this._speed;
           };
    
           // 
           this.dispose = function(){
             this.disposeElements();
             Game.unregisterElement(this);
             for(var f in this){
               if('function' == typeof this[f]){
                 this[f] = function(){};
               }
             }
           };
    
           this.disposeElements = function(){
             var img = this._elem;
             if(img.parentNode) img.parentNode.removeChild(img);
           };
           
           // フレームごとの計算結果を元に
           this.redraw = function(){
             var img = this._elem;
             if(this._dispose){
               this.dispose();
               return;
             }
             var sx  = this._x;
             var sy  = this._y;
             img.style.top  = (sy - img.clientHeight / 2) + 'px';
             img.style.left = (sx - img.clientWidth / 2) + 'px';
           };
    
           // 当たり判定の対象となるクラス群
           this.getTargetClasses = function(){
             return [];
           };
           
           // 自身のフレームごとの軌跡
           this.getTrack = function(){
             var R = 0;
             return R;
           };
    
           // 当たり判定
           this.getHitRectangle = function(){
             var img = this._elem;
             var w = img.clientWidth;
             var h = img.clientHeight;
             var sx = this._x;
             var sy = this._y;
             var x0 = sx - w / 2;
             var y0 = sy - h / 2;
             return { 
               x1: x0, 
               y1: y0,
               x2: x0 + w,
               y2: y0 + h
             };
           };
    
           // elem との当り判定を書く
           // TODO すりぬけない方法について学習する
            this.checkHit = function(elem){
              var sr = this.getHitRectangle();
              var er = elem.getHitRectangle();
              var x0 = sr.x1;
              var x1 = sr.x2;
              var x2 = er.x1;
              var x3 = er.x2;
              var y0 = sr.y1;
              var y1 = sr.y2;
              var y2 = er.y1;
              var y3 = er.y2;
              return (x0 < x3 && x2 < x1 && y0 < y3 && y2 < y1);
            };
    
            // elem 衝突時の振舞い
            this.onHit = function(elem){
            };
    
            this.onScreen = function(){
              var sx = this._x;
              var sy = this._y;
              return (sx >= 0 && sx < window.innerWidth &&
                      sy >= 0 && sy < window.innerHeight);
            };
    
    
            this.backToScreen = function(){
              var sx = this._x;
              var sy = this._y;
              if(sx < 0){
                this._x = 0;
              } else if (sx >= window.innerWidth){
                this._x = window.innerWidth - 1;
              }
              if(sy < 0){
                this._y = 0;
              } else if (sy >= window.innerHeight){
                this.y = window.innerHeight - 1;
              }
            };
    
          }
        );
    
       var Explode = defclass(
         {
           base: GameElement,
           name: 'Explode'
         },
    
         function(SUPER, Class){
           this.initialize = function(x, y){
             SUPER.initialize.apply(this, [x, y]);
             this._frame = 0;
             this._stars = null;
           };
    
           this.makeElem = function(){
             var div = document.createElement('div');
             div.style.width  = '100px';
             div.style.height = '100px';
             var img  = genImage('http://s.hatena.ne.jp/images/star-red.gif', div);
             img.style.position = 'absolute';
             var imgs = [ img ];
             this.stars = imgs;
             for(var i =0; i < 15; i++){
               var clone = img.cloneNode(true);
               imgs.push( clone );
               div.appendChild( clone );
             }
             return div;
           };
    
           this.step   = function(){
             this._frame++;
             if(this._frame > 40){
               this.dispose();
             }
           };
    
           this.redraw = function(){
             SUPER.redraw.apply(this, []);
             var imgs = this.stars;
             var frame = this._frame;
             var sx    = this._x;
             var sy    = this._y;
             for(var i = 0;i<16;i++){
               var rad = i / 16 * 2 * Math.PI;
               var x = (Math.cos(rad)) * frame;
               var y = (Math.sin(rad)) * frame;;
               imgs[i].style.top  = (50 + y) + 'px';
               imgs[i].style.left = (50 + x) + 'px';
             }
           };
         }
    
       );
    
    
    
       ////////////////////////////////////////////////////////////
       var Bullet = defclass(
         {
           base: GameElement,
           name: 'Bullet'
         },
         function(SUPER, Class){
           this.initialize = function(x,y){
             SUPER.initialize.apply(this,[x, y]);
             this.setDirection(0,-1);
             this.setSpeed(10);
           };
    
    
           this.step = function(){
             SUPER.step.apply(this, []);
             if(!this.onScreen()) this._dispose = true;
           };
         }
       );
    
       var EBullet = defclass(
         {
           base: Bullet,
           name: 'EBullet'
         },
         function(SUPER, Class){
           this.makeElem = function(){
             return genImage('http://s.hatena.ne.jp/images/star.gif');
           };
         }
       );
    
       var PBullet = defclass(
         {
           base: Bullet,
           name: 'PBullet'
         },
         function(SUPER, Class){
           this.makeElem = function(){
             return genImage('http://s.hatena.ne.jp/images/star-green.gif');
           };
         }
       );
    
       ////////////////////////////////////////////////////////////
       var Motion;
       var RandomMotion;
    
       var User = defclass(
         {
           base: GameElement,
           name: 'User'
         }, function(SUPER,Class){
    
           this.initialize = function(name){
             this.name    = name;
             SUPER.initialize.apply(this,[0,0]);
             this._defaultSpeed = 5;
             this.setDirection(0, -1);
             this.setSpeed(0);
           };
    
           this.makeElem = function(){
             var name = this.name;
             return genImage(
               'http://www.st-hatena.com/users/' + name.substring(0,2) +
                 '/' + name + '/profile.gif'
             );
           };
    
           this.explode = function(){
             new Explode(this._x, this._y);
             this.dispose();
           };
            
         }
       );
    
       var Enemy = defclass(
         {
           base: User,
           name: 'Enemy'
         },
         function(SUPER, Class){
           this.initialize = function(name){
             SUPER.initialize.apply(this,[name]);
             var img = this._elem;
             this._x = Math.floor((window.innerWidth - img.clientWidth ) / 2
                                 );
             this._y = img.clientHeight;
             this._motion = new RandomMotion( this );
           };
    
           this.setMotion = function(motion){
             this._motion = motion;
           };
    
           this.step = function(){
             this._motion.step();
             SUPER.step.apply(this, []);
             if(!this.onScreen()){
               this.backToScreen();
             }
           };
    
           this.getTargetClasses = function(){
             return ['PBullet'];
           };
    
           this.onHit = function(elem){
             if(elem instanceof PBullet){
               this.explode();
             }
           };
         }
       );
    
       var Player = defclass(
         {
           base: User,
           name: 'Player'
         }, function(SUPER, Class){
    
           this.initialize = function(name){
             SUPER.initialize.apply(this,[name]);
             var self = this;
             var img = this._elem;
             this._arrowHook = function(){
               var dir = this.getCurrentDirection();
               self.setDirection(dir.x, dir.y);
             };
             Game.arrowKeyManager.addHook( this._arrowHook );
             this._shotHook = function(){ self.shot(); };
             Game.shotKeyManager.addHook( this._shotHook );
             this._x      =
               Math.floor((window.innerWidth - img.clientWidth ) / 2
                         );
             this._y      =
               (window.innerHeight - img.clientHeight - 10);
           };
    
           this.dispose = function(){
             Game.arrowKeyManager.deleteHook( this._arrowHook );
             Game.shotKeyManager.deleteHook( this._shothook );
             SUPER.dispose.apply(this, []);
           };
    
           this.getTargetClasses = function(){
             return ['Enemy', 'EBullet'];
           };
    
           this.onHit = function(elem){
             if(elem instanceof Enemy){
               this.explode();
               elem.explode();
             }
             if(elem instanceof EBullet){
               this.explode();
             }
           };
    
           this.shot = function(){
             var pos = this.getPosition();
             var bullet = new PBullet(pos.x, pos.y);
             var dir = this.getDirection();
             bullet.setDirection(dir.x, dir.y);
             bullet.setSpeed(this._defaultSpeed + 2);
           };
    
           this.setDirection = function(x, y){
             if(x == 0 && y == 0){
               this.setSpeed(0);
             } else {
               this.setSpeed(this._defaultSpeed);
               SUPER.setDirection.apply(this, [x,y]);
             }
           };
         }
       );
    
       ////////////////////////////////////////////////////////////
    
       Motion = defclass(
         {
           base: Object,
           name: 'Motion'
         },
         function(SUPER, Class){
           this.initialize = function(elem){
             this.elem = elem;
           };
           this.step = function(){
           };
         }
       );
    
       RandomMotion = defclass(
         {
           base: Motion,
           name: 'RandomMotion'
         },
         function(SUPER, Class){
           this.initialize = function(elem){
             SUPER.initialize.apply(this, [elem]);
             this._turn_at = null;
             this._iter    = 0;
             this._changeDir();
           };
    
           this._changeDir = function(){
             this._turn_at = 50;
             var elem = this.elem;
             var player = null;
             if(this._iter++ > 3 &&
                Math.random() * 3 < 1){
               player = (Game.getElementsByClass(Player) || [])[0] || null;
             }
             this._iter++;
             if(player){
               var dx = player._x - elem._x;
               var dy = player._y - elem._y;
               var ddx, ddy;
               if(Math.abs(dx) > Math.abs(dy)){
                 ddx = dx < 0 ? -1 : 1;
                 if(dy == 0){
                   ddy = 0;
                 } else {
                   ddy = dy / Math.abs(dx);
                 }
               }else{
                 ddy = dy < 0 ? -1 : 1;
                 if(dx == 0){
                   ddx = 0;
                 } else{
                   ddx = dx / Math.abs(dy);
                 }
               }
               elem.setDirection(ddx, ddy);
             } else {
               elem.setDirection(Math.random() * 2 - 1,
                                 Math.random() * 2 - 1);
             }
             elem.setSpeed(Math.random() * 10);
           };
    
           this.step = function(){
             switch(this._turn_at){
               case 40:
               case 30:
               case 20:
               case 10:
               case 0:
               this.shot();
             }
             if(this._turn_at-- < 0){
               this._changeDir();
             }
           };
    
    
           this.shot = function(){
             var pos = this.elem.getPosition();
             var bullet = new EBullet(pos.x, pos.y);
             var dir = this.elem.getDirection();
             bullet.setDirection(dir.x, dir.y);
             bullet.setSpeed(this.elem.getSpeed() + 5);
           };
    
         }
       );
    
       ////////////////////////////////////////////////////////////
       
       var name = document.evaluate(
         './/p[@class="username"]/a/text()',
            document.body,
         null,
         XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
         null).snapshotItem(0).data;
    
       var user  = new Player(name);
       var enemy = new Enemy('default');
       Game.start();
       
     })();
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2010/12/30 15:12:55 - 2010-12-30
  2. 2010/12/18 15:13:34 - 2010-12-18
  3. 2010/12/18 15:11:50 - 2010-12-18
  4. 2010/12/18 05:40:03 - 2010-12-18
  5. 2010/12/18 03:58:19 - 2010-12-18
  6. 2010/12/18 03:47:07 - 2010-12-18
  7. 2010/12/18 02:09:19 - 2010-12-18
  8. 2010/12/18 00:16:28 - 2010-12-18
  9. 2010/12/16 23:55:19 - 2010-12-16
  10. 2010/12/16 22:56:54 - 2010-12-16
  11. 2010/12/15 21:59:33 - 2010-12-15
  12. 2010/12/15 04:50:59 - 2010-12-15
  13. 2010/12/14 23:45:13 - 2010-12-14
  14. 2010/12/14 23:27:59 - 2010-12-14
  15. 2010/12/14 23:18:54 - 2010-12-14