はてなハイク STAR FIGHTERS
by
lieutar
2010-12-30 [2010/12/30 15:12:55]
はてなハイクで実行すると、星を打ちあう簡単な対戦型シューティングが初まる。カーソルキーで移動、Zで星発射。ESCで終了
@@ -6,56 +6,136 @@
* @require
*/
+
+
+
(function(){
- var Z_INDEX_USER = 65536;
- var Z_INDEX_BULLET = 65535;
-
- var inherit = function(parent, initializer){
+
+ 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 = parent.prototype;
+ 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,
[
- parent.prototype,
+ base.prototype,
R
]
);
+
return R;
};
var Game = new (
- inherit(
- Object, function(SUPER, Class){
+ defclass(
+ {
+ base: Object,
+ name: 'Game'
+ },
+ function(SUPER, Class){
this.initialize = function(){
- this.elems = {};
+ this.allElems = {};
+ this.elemDic = {};
};
this.registerElement = function(elem){
- this.elems[elem.key] = 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.elems[elem.key];
+ 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 elems = this.elems;
- var f;
- for(f in elems){
- elems[f].step();
+ var allElems = this.allElems;
+ var elemDic = this.elemDic;
+ var i,l, ff;
+ var elem;
+ for(f in allElems){
+ allElems[f].step();
}
- // 衝突検知と、衝突後の処理を書く
- for(f in elems){
- elems[f].redraw();
+ 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++){
+ var edslot = elemDic[tclasses[i]];
+ for(ff in edslot){
+ if(chslot[ff]) continue;
+ chslot[ff] = true;
+ var target = edslot[f];
+ 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();
}
};
@@ -66,12 +146,16 @@
setTimeout(arguments.callee, 10);
})();
};
- }
- ));
+ }) // end of defclass
+ ); // end of new
- var GameElement = inherit(
- Object, function(SUPER, Class){
+ var GameElement = defclass(
+ {
+ base: Object,
+ name: 'GameElement'
+ },
+ function(SUPER, Class){
var counter = 0;
this.initialize = function(x,y){
@@ -89,7 +173,6 @@
Game.registerElement(this);
};
-
this.initImage = function(url){
var img = document.createElement('img');
document.body.appendChild(img);
@@ -111,7 +194,9 @@
};
};
- this.getSpeed = function(){ return this._speed; };
+ this.getSpeed = function(){
+ return this._speed;
+ };
this.setDirection = function(x, y){
this._dx = x;
@@ -131,6 +216,7 @@
return { x:this._pdx, y:this._pdy };
};
+ // ステップごとの自己状態の変更
this.step = function(){
this._pre_x = this._x;
this._pre_y = this._y;
@@ -138,11 +224,18 @@
this._y = this._y + this._dy * this._speed;
};
+ //
+ this.dispose = function(){
+ var img = this._img;
+ img.parentNode.removeChild(img);
+ };
+
+ // フレームごとの計算結果を元に
this.redraw = function(){
var img = this._img;
if(this._dispose){
Game.unregisterElement(this);
- img.parentNode.removeChild(img);
+ this.dispose();
return;
}
var sx = this._x;
@@ -151,12 +244,24 @@
img.style.left = (sx - img.clientWidth / 2) + 'px';
};
+ // 当たり判定の対象となるクラス群
+ this.getTargetClasses = function(){
+ return [];
+ };
+
+ // 自身のフレームごとの軌跡
this.getTrack = function(){
var R = 0;
return R;
};
- this.onHit = function(){
+ // elem との当り判定を書く
+ this.checkHit = function(elem){
+ return false;
+ };
+
+ // elem 衝突時の振舞い
+ this.onHit = function(elem){
};
this.onScreen = function(){
@@ -168,8 +273,12 @@
}
);
- var Bullet = inherit(
- GameElement, function(SUPER, Class){
+ 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);
@@ -184,8 +293,11 @@
}
);
- var User = inherit(
- GameElement, function(SUPER,Class){
+ var User = defclass(
+ {
+ base: GameElement,
+ name: 'User'
+ }, function(SUPER,Class){
this.initialize = function(name){
SUPER.initialize.apply(this,[0,0]);
@@ -212,12 +324,6 @@
};
};
- this.getSpeed = function(){
- var dir = this.getDirection();
- if(dir.x == 0 && dir.y == 0) return 0;
- return SUPER.getSpeed.apply(this);
- };
-
this.shot = function(){
var pos = this.getPosition();
var bullet = new Bullet(pos.x, pos.y);
@@ -226,10 +332,28 @@
bullet.setSpeed(this.getSpeed() + 2);
};
- this.setDirection = function(x,y){
- SUPER.setDirection.apply(this, [x,y]);
- if(x != 0 || y != 0) this.setShotDirection(x, y);
- };
+
+ this.setDirection = (
+ function(){
+ var timer = null;
+ return function(x,y){
+ var self = this;
+ SUPER.setDirection.apply(this, [x,y]);
+ if(x != 0 || y != 0){
+ if(timer){
+ clearTimeout(timer);
+ timer = null;
+ } else {
+ timer = setTimeout(
+ function(){
+ self.setShotDirection(x, y);
+ timer = null;
+ },
+ 50);
+ }
+ }
+ };
+ })();
this.move = function(dir){
var _move_k = this._move_k;
@@ -311,11 +435,10 @@
case 40: //DOWN
user.move('s');
break;
- case 90: // Z
- user.shot();
}
}, true
);
+
window.addEventListener(
'keyup', function(e){
@@ -324,7 +447,7 @@
switch(e.keyCode){
case 37: //LEFT
user.move('W');
- break;
+ break;
case 38: //UP
user.move('N');
break;
@@ -334,6 +457,8 @@
case 40: //DOWN
user.move('S');
break;
+ case 90: // Z
+ user.shot();
}
}, true
);
/*
* @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 Game = new (
defclass(
{
base: Object,
name: 'Game'
},
function(SUPER, Class){
this.initialize = function(){
this.allElems = {};
this.elemDic = {};
};
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++){
var edslot = elemDic[tclasses[i]];
for(ff in edslot){
if(chslot[ff]) continue;
chslot[ff] = true;
var target = edslot[f];
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 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._spped = 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;
};
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(){
var img = this._img;
img.parentNode.removeChild(img);
};
// フレームごとの計算結果を元に
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.getTargetClasses = function(){
return [];
};
// 自身のフレームごとの軌跡
this.getTrack = function(){
var R = 0;
return R;
};
// elem との当り判定を書く
this.checkHit = function(elem){
return false;
};
// 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);
};
}
);
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;
};
}
);
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.setSpeed(5);
this._move_k = {n:0,s:0,w:0,e:0};
this._x =
Math.floor((window.innerWidth - img.clientWidth ) / 2
);
this._y =
(window.innerHeight - img.clientHeight - 10);
this.setShotDirection(0,-1);
};
this.getHotSpot = function(){
var img = this._img;
return {
x: Math.floor(this._x + img.clientWidth / 2),
y: Math.floor(this._y + img.clientHeight / 2)
};
};
this.shot = function(){
var pos = this.getPosition();
var bullet = new Bullet(pos.x, pos.y);
var dir = this.getShotDirection();
bullet.setDirection(dir.x, dir.y);
bullet.setSpeed(this.getSpeed() + 2);
};
this.setDirection = (
function(){
var timer = null;
return function(x,y){
var self = this;
SUPER.setDirection.apply(this, [x,y]);
if(x != 0 || y != 0){
if(timer){
clearTimeout(timer);
timer = null;
} else {
timer = setTimeout(
function(){
self.setShotDirection(x, y);
timer = null;
},
50);
}
}
};
})();
this.move = function(dir){
var _move_k = this._move_k;
switch(dir){
case 'n':
_move_k.n = 1;
if(_move_k.s > 0) _move_k.s = 2;
break;
case 'N':
_move_k.n = 0;
if(_move_k.s > 0) _move_k.s = 1;
break;
case 's':
_move_k.s = 1;
if(_move_k.n > 0) _move_k.n = 2;
break;
case 'S':
_move_k.s = 0;
if(_move_k.n > 0) _move_k.n = 1;
break;
case 'w':
_move_k.w = 1;
if(_move_k.e > 0) _move_k.e = 2;
break;
case 'W':
_move_k.w = 0;
if(_move_k.e > 0) _move_k.e = 1;
break;
case 'e':
_move_k.e = 1;
if(_move_k.w > 0) _move_k.w = 2;
break;
case 'E':
_move_k.e = 0;
if(_move_k.w > 0) _move_k.w = 1;
}
var x = 0;
var y = 0;
if(_move_k.s != _move_k.n){
y = _move_k.s > _move_k.n ? 1 : -1;
}
if(_move_k.w != _move_k.e){
x = _move_k.e > _move_k.w ? 1 : -1;
}
this.setDirection(x,y);
};
}
);
var name = document.evaluate(
'.//p[@class="username"]/a/text()',
document.body,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null).snapshotItem(0).data;
var user = new User(name);
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
user.move('w');
break;
case 38: //UP
user.move('n');
break;
case 39: //RIGHT
user.move('e');
break;
case 40: //DOWN
user.move('s');
break;
}
}, true
);
window.addEventListener(
'keyup', function(e){
e.stopPropagation();
e.preventDefault();
switch(e.keyCode){
case 37: //LEFT
user.move('W');
break;
case 38: //UP
user.move('N');
break;
case 39: //RIGHT
user.move('E');
break;
case 40: //DOWN
user.move('S');
break;
case 90: // Z
user.shot();
}
}, true
);
Game.start();
})();
- Permalink
- このページへの個別リンクです。
- RAW
- 書かれたコードへの直接のリンクです。
- Packed
- 文字列が圧縮された書かれたコードへのリンクです。
- Userscript
- Greasemonkey 等で利用する場合の .user.js へのリンクです。
- Loader
- @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
- Metadata
- コード中にコメントで @xxx と書かれたメタデータの JSON です。