はてなハイク STAR FIGHTERS
by
lieutar
2010-12-30 [2010/12/30 15:12:55]
はてなハイクで実行すると、星を打ちあう簡単な対戦型シューティングが初まる。カーソルキーで移動、Zで星発射。ESCで終了
@@ -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 です。