0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

javascriptで作るゲーム【ジャンケンマンフィーバー】表示処理編

Last updated at Posted at 2024-05-05

前回まではこちらを参照してください。
https://qiita.com/tri-comma/items/e9790b420d9786f91149

表示は何で実装しようか考えた結果、canvasにしました。
まだメダル枚数表示とか音とか、実装すべきものは残ってますが、いったん出来たところまでで記事にします。
最後に動くソース一式を掲載しますが、まずはhtmlからどうぞ。

HTML部分

シンプル。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ジャンケンマンフィーバー2024</title>
    <style>
body {
	margin: 0;
	overflow: hidden;
}
#canvas {
	margin: auto;
	width: 100vh;
	height: 100vh;
}
#base {
	display: none;
}
    </style>
</head>
<body>
	<canvas id="canvas"></canvas>
	<img id="base" src="rps.png" />
	<script>
// ここに処理を書きます
    </script>
</body>

</html>

描画領域のcanvasタグと、描画素材のimgタグがあるだけです。
ちなみに画像はこんな感じ。
(DALL-E3で作成したものをレタッチ。何気にこれが一番時間かかったんじゃないか?)

rps.png

ゲームロジック部分

前回から少し変更があるので再掲します。

class JankenFever {
	#status;
	constructor(ms = 100) {
		this.STS = { STANDBY: 0, PLAYING: 1, SPINING: 2 };
		this.RSP = [ 'R', 'S', 'P' ];
		this.ROULETTE = [1, 2, 4, 7, 2, 4, 1, 2, 7, 4, 2, 20];
		this.RESULT = {
			WIN:  { val:'WIN',  next: this.STS.SPINING, callback: this.onwin  },
			LOSE: { val:'LOSE', next: this.STS.STANDBY, callback: this.onlose },
			DRAW: { val:'DRAW', next: this.STS.PLAYING, callback: this.ondraw },
		};
		this.JUDGE = {
			'RR': this.RESULT.DRAW,	'SS': this.RESULT.DRAW, 'PP': this.RESULT.DRAW,
			'RS': this.RESULT.WIN, 	'SP': this.RESULT.WIN, 	'PR': this.RESULT.WIN,
			'RP': this.RESULT.LOSE,	'SR': this.RESULT.LOSE, 'PS': this.RESULT.LOSE,		
		};
		this.status = this.STS.STANDBY;
		this.medal = 1000;
		this.player = { medal: 10 };
		this.onstart = null;
		this.onspin = null;
		this.onstop = null;
		this.interval = ms;
		this.ridx = 0;
	}
	set status(sts) {
		this.#status = sts;
		if (sts === this.STS.SPINING) this.spin();
	}
	get status() { return this.#status;	}
	set onwin(fn)  { this.RESULT.WIN.callback = fn; }
	set onlose(fn) { this.RESULT.LOSE.callback = fn; }
	set ondraw(fn) { this.RESULT.DRAW.callback = fn; }
	start() {
		if (this.status === this.STS.STANDBY && this.insertMedal(this.player)) {
			this.status = this.STS.PLAYING;
			this.onstart ? this.onstart() : null;
			return { result: true, medal: this.medal, pmedal: this.player.medal };
		} else { return { reslut: false, status: this.status, medal: this.medal, pmedal: this.player.medal }; }
	}
	play(yourHand) {
		if (this.status == this.STS.PLAYING && this.RSP.indexOf(yourHand) > -1) {
			const myHand = this.RSP[this.rnd(this.rnd(this.RSP.length))];
			const result = this.judge(myHand, yourHand);
			this.status = result.next;
			result.callback ? result.callback() : null;
			return { result: result.val, medal: this.medal, pmedal: this.player.medal };
		} else { return { result: false, status: this.status, yourHand }; }
	}
	spin() {
		if (this.status == this.STS.SPINING) {
			this.ridx = this.rnd(this.ROULETTE.length);
			this.scnt = 0;
			this.riid = setInterval(()=>{
				this.onspin ? this.onspin(this.ridx, this.ROULETTE) : null;
				this.ridx = (this.ridx + 1) % this.ROULETTE.length;
				if (this.scnt++ > 31) {
					clearInterval(this.riid);
					this.stop();
				}
			}, this.interval);
			return { result: true, medal: this.medal, pmedal: this.player.medal };
		} else { return { result: false, status: this.status }; }
	}
	stop() {
		if (this.status == this.STS.SPINING) {
			const medal = this.ROULETTE[this.ridx];
			this.medal -= medal;
			this.player.medal += medal;
			this.status = this.STS.STANDBY;
			this.onstop ? this.onstop() : null;
			return { result: true, medal: this.medal, pmedal: this.player.medal };
		} else { return { result: false, status: this.status }; }
	}
	insertMedal(player) {
		if (player.medal > 0 && this.medal > Math.max.apply(null,this.ROULETTE)) {
			player.medal--;
			this.medal++;
			return true;
		}
		return false;
	}
	judge(myHand, yourHand) { return this.JUDGE[myHand+yourHand]; }
	rnd(m) { return Math.floor(Math.random()*m); }
}

変更点

  • spinメソッドを変更: onspinイベントを一定周期で発生させる、規定回数が来たらstopメソッドを呼び出す
  • stopメソッドを追加: メダルの払い出しとonstopイベントの呼び出し、ステータスをスタンバイに変更
  • コンストラクタにonspinの発生周期(ミリ秒)を指定できるように変更

表示制御部分

ちょっと長いですけど、まずはソースから。
このあと簡単な解説もあります。

class JFView {
	constructor(cid, iid) {
		this.game = new JankenFever(100);
		this.game.onstart = ()=>this.drawComThinking();
		this.game.onwin = ()=>{
			clearInterval(this.plyItvID);
			this.canInsertMedal = true;
			this.blinkResult('win');
		};
		this.game.onlose = ()=>{
			clearInterval(this.plyItvID);
			this.canInsertMedal = true;
			this.blinkResult('lose');
			this.plyItvID = setTimeout(()=>this.drawDemo(), 4000);
		};
		this.game.ondraw = ()=>{
			clearInterval(this.plyItvID);
			this.blinkResult('draw');
			this.plyItvID = setTimeout(()=>this.drawComThinking(false), 1500);
		};
		this.game.onspin = (ri, rm) => this.drawRoulette(ri, rm);
		this.game.onstop = () => this.plyItvID = setTimeout(()=>this.drawDemo(), 3000);
		this.canvas = document.getElementById(cid);
		this.ctx = this.canvas.getContext('2d');
		this.image = document.getElementById(iid);
		this.canvas.width = 1024 * (window.innerWidth / window.innerHeight);
		this.canvas.height = 1024;
		this.canvas.style.width = window.innerWidth + 'px';
		this.canvas.style.height = window.innerHeight + 'px';
		this.wmgn = Math.floor((this.canvas.width - 1024) / 2);
		this.image.addEventListener('load', e=>this.drawInit());
		this.canInsertMedal = true;
		this.plyItvID;
		this.resItvID;
		this.myHand = '';
		this.cx = (canvas.width - 1024) / 2 + 536;
		this.cy = 411;
		this.r = 130;
		this.degs = [-76,-47,-15,17,49,77,102,131,163,-165,-133,-101];
		this.fs = 28;
		this.ctx.font = `bold ${this.fs}px Impact`;
		this.ctx.lineWidth = 2.5;
		this.canvas.addEventListener('click', (e) => {
			const cvsx = Math.floor(1024 * (e.x / window.innerHeight) - this.wmgn);
			const cvsy = Math.floor(1024 * (e.y / window.innerHeight));
			if (cvsx>440 && cvsx<615 && cvsy>705 && cvsy<925 && this.canInsertMedal) {
				clearInterval(this.plyItvID);
				this.game.start();
			}
			if (cvsx>355 && cvsx<441 && cvsy>586 && cvsy<632) this.onpress('R');
			if (cvsx>487 && cvsx<573 && cvsy>586 && cvsy<632) this.onpress('S');
			if (cvsx>621 && cvsx<707 && cvsy>586 && cvsy<632) this.onpress('P');
		});
	}
	drawInit() {
		const { ctx, image, wmgn } = this;
		ctx.drawImage(image, 0, 0, 1, 1024, 0, 0, wmgn + 1, 1024);
		ctx.drawImage(image, 1022, 0, 1, 1024, wmgn + 1023, 0, wmgn + 1, 1024);
		ctx.drawImage(image, 0, 0, 1024, 1024, wmgn, 0, 1024, 1024);
		this.drawDemo();
	}
	drawDemo() {
		this.drawComHand(0);
		this.plyItvID = setInterval((()=>{
			let rsp = 0;
			return () => this.drawComHand(rsp = (rsp + 1) % 3);
		})(), 1000);	
	}
	drawComHand(rsp) {
		const { ctx, image, wmgn, game } = this;
		const rsp_xy = [ {x:1024,y:0}, {x:1024,y:170}, {x:1024,y:340} ][rsp];
		ctx.drawImage(image, rsp_xy.x, rsp_xy.y, 150, 170, wmgn + 448, 317, 150, 170);
		this.drawRoulette(-1, game.ROULETTE);
	}
	drawRoulette(rt = -1, rtmst) {
		const { degs, ctx, cx, cy, r, fs } = this;
		let i = 0;
		rtmst.forEach((num)=>{
			const [numStr, rad] = [num + '', Math.PI / 180 * degs[i]];
			ctx.strokeStyle = rt===i ? '#FF0' : ['#C22','#C22'][i%2];
			ctx.fillStyle = rt===i ? '#F00' : ['#FFF','#FFC'][i%2];
			const dx = Math.floor(cx + r * Math.cos(rad) - (fs * numStr.length) / 2);
			const dy = Math.floor(cy + r * Math.sin(rad) * 185/210);
			ctx.strokeText(numStr, dx, dy);
			ctx.fillText(numStr, dx, dy);
			i++;
		});
	}
	onpress(rsp) {
		this.drawButton(rsp);
		if (!this.canInsertMedal) this.game.play(this.myHand = rsp);
	}
	drawButton(rsp) {
		const { ctx, image, wmgn } = this;
		const xy = {
			R: { px:1024, py:614, bx:355, by:586 },
			S: { px:1024, py:660, bx:487, by:586 },
			P: { px:1024, py:706, bx:621, by:586 }
		}[rsp];
		ctx.drawImage(image, xy.px, xy.py, 86, 46, wmgn + xy.bx, xy.by, 86, 46);
		setTimeout( () => ctx.drawImage(image, xy.bx, xy.by, 86, 46, wmgn + xy.bx, xy.by, 86, 46), 150);
	}
	drawComThinking(flush = true) {
		this.canInsertMedal = false;
		flush ? this.blinkOff() : null;
		let rsp = 0;
		this.plyItvID = setInterval(() => {
			rsp = ([0,1,2].filter(i=>i!==rsp))[Math.round(Math.random())];
			this.drawComHand(rsp);
		}, 100);
	}
	blinkOff() {
		const { resItvID, ctx, image, wmgn } = this;
		clearInterval(resItvID);
		ctx.drawImage(image, 343, 261, 68, 52, wmgn + 343, 261, 68, 52);
		ctx.drawImage(image, 646, 260, 68, 52, wmgn + 646, 260, 68, 52);
		ctx.drawImage(image, 343, 503, 68, 52, wmgn + 343, 503, 68, 52);
		ctx.drawImage(image, 646, 503, 68, 52, wmgn + 646, 503, 68, 52);
	}
	blinkResult(result) {
		const handSels = { win:{'R':1,'S':2,'P':0}, lose:{'R':2,'S':0,'P':1}, draw:{'R':0,'S':1,'P':2} };
		this.drawComHand(handSels[result][this.myHand]);
		this.blinkOff();
		const drawBlink = this.getDrawBlink(result);
		drawBlink();
		this.resItvID = setInterval(drawBlink, 500);
	}
	getDrawBlink(result) {
		const { wmgn, ctx, image } = this;
		const self = this;
		const p = [{
				winl:[1024, 510, 68, 52, wmgn + 343, 261, 68, 52],
				winr:[1092, 510, 68, 52, wmgn + 646, 260, 68, 52],
				lose:[1024, 562, 68, 52, wmgn + 343, 503, 68, 52],
				draw:[1092, 562, 68, 52, wmgn + 646, 503, 68, 52],
			},
			{
				winl:[343, 261, 68, 52, wmgn + 343, 261, 68, 52],
				winr:[646, 260, 68, 52, wmgn + 646, 260, 68, 52],
				lose:[343, 503, 68, 52, wmgn + 343, 503, 68, 52],
				draw:[646, 503, 68, 52, wmgn + 646, 503, 68, 52],
			}];
		let i = 0;
		return function() {
			i++;
			let d = p[i % 2][result === 'win' ? 'winl' : result];
			ctx.drawImage(image, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8]);
			if (result === 'win') {
				d = p[i % 2]['winr'];
				ctx.drawImage(image, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8]);
			}
			if (i === 7) clearInterval(self.resItvID);
		};
	}
}

const view = new JFView('canvas', 'base');

解説

概要

  • canvasに、あらかじめ用意した画像をペタペタする方針
  • ルーレット部分だけはText描画
  • ライブラリの類は使いませんでした

コンストラクタ

  • View制御クラスの中でゲームロジックの制御クラスをインスタンス化
    • ロジックのコールバックを登録
      • onstart: グーチョキパーをてけてけする
      • onwin: WINをピカピカする
      • onlose: LOSEをピカピカする、ピカピカが終わったらデモに戻る
      • ondraw: DRAWをピカピカする
      • onspin: ルーレット表示
      • onstop: デモに戻る
    • canvas初期化: 描画領域とウィンドウ領域のアスペクト比あわせ、初期描画
    • テキスト描画のための各種初期化
    • クリックイベントの登録

メソッド

  • drawInit: 初期描画、ゲームの有効領域は1:1のため横長の場合はパターンで埋める
  • drawDemo: メダルを入れるまでの、ゲームデモ画面。グーチョキパーをゆっくり繰り返す
  • drawComHand: 画面中央にグー/チョキ/パーのいずれかを表示する処理
  • drawRoulette: 12個の数字を描画する
  • onpress: グーチョキパーボタンを押した時の処理
  • drawButton: ボタンをポチッとな、の描画処理
  • drawComThinking: グーチョキパーを高速描画
  • blinkOFF: WIN/LOSE/DRAWのピカピカを止める
  • blinkResult: WIN/LOSE/DRAWのピカピカ描画処理
  • getDrawBlink: ピカピカ描画処理の本体(functionを返却)

ポイント

  • 繰り返しになりますけど「メダル枚数表示」とか、まだ未完成部分がありますよ
  • iOS Safariだとwindowのclickイベントは処理できない → canvasでaddEventListenerする
  • 画面全体を描画領域にしてみた。余白を埋めたり座標変換したり、まあまあ頭使いました
  • ルーレットの数字描画、最終的には数字ごとに角度を決めることで調整(degs定義を参照)
  • windowのリサイズには対応していない(そこまで需要ないでしょ)
  • 可読性を損なわない程度に短くしたつもり(描画のあたりはもうちょっとどうにかできそう)
  • 貴重な人生の時間をジャンケンゲーム制作に費やすアラフィフ

すべてのソース

以下のソースと画像ファイルがあればゲーム試せます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ジャンケンマンフィーバー2024</title>
    <style>
body {
	margin: 0;
	overflow: hidden;
}
#canvas {
	margin: auto;
	width: 100vh;
	height: 100vh;
}
#base {
	display: none;
}
    </style>
</head>
<body>
	<canvas id="canvas"></canvas>
	<img id="base" src="rps.png" />
	<script>
class JankenFever {
	#status;
	constructor(ms = 100) {
		this.STS = { STANDBY: 0, PLAYING: 1, SPINING: 2 };
		this.RSP = [ 'R', 'S', 'P' ];
		this.ROULETTE = [1, 2, 4, 7, 2, 4, 1, 2, 7, 4, 2, 20];
		this.RESULT = {
			WIN:  { val:'WIN',  next: this.STS.SPINING, callback: this.onwin  },
			LOSE: { val:'LOSE', next: this.STS.STANDBY, callback: this.onlose },
			DRAW: { val:'DRAW', next: this.STS.PLAYING, callback: this.ondraw },
		};
		this.JUDGE = {
			'RR': this.RESULT.DRAW,	'SS': this.RESULT.DRAW, 'PP': this.RESULT.DRAW,
			'RS': this.RESULT.WIN, 	'SP': this.RESULT.WIN, 	'PR': this.RESULT.WIN,
			'RP': this.RESULT.LOSE,	'SR': this.RESULT.LOSE, 'PS': this.RESULT.LOSE,		
		};
		this.status = this.STS.STANDBY;
		this.medal = 1000;
		this.player = { medal: 10 };
		this.onstart = null;
		this.onspin = null;
		this.onstop = null;
		this.interval = ms;
		this.ridx = 0;
	}
	set status(sts) {
		this.#status = sts;
		if (sts === this.STS.SPINING) this.spin();
	}
	get status() { return this.#status;	}
	set onwin(fn)  { this.RESULT.WIN.callback = fn; }
	set onlose(fn) { this.RESULT.LOSE.callback = fn; }
	set ondraw(fn) { this.RESULT.DRAW.callback = fn; }
	start() {
		if (this.status === this.STS.STANDBY && this.insertMedal(this.player)) {
			this.status = this.STS.PLAYING;
			this.onstart ? this.onstart() : null;
			return { result: true, medal: this.medal, pmedal: this.player.medal };
		} else { return { reslut: false, status: this.status, medal: this.medal, pmedal: this.player.medal }; }
	}
	play(yourHand) {
		if (this.status == this.STS.PLAYING && this.RSP.indexOf(yourHand) > -1) {
			const myHand = this.RSP[this.rnd(this.rnd(this.RSP.length))];
			const result = this.judge(myHand, yourHand);
			this.status = result.next;
			result.callback ? result.callback() : null;
			return { result: result.val, medal: this.medal, pmedal: this.player.medal };
		} else { return { result: false, status: this.status, yourHand }; }
	}
	spin() {
		if (this.status == this.STS.SPINING) {
			this.ridx = this.rnd(this.ROULETTE.length);
			this.scnt = 0;
			this.riid = setInterval(()=>{
				this.onspin ? this.onspin(this.ridx, this.ROULETTE) : null;
				this.ridx = (this.ridx + 1) % this.ROULETTE.length;
				if (this.scnt++ > 31) {
					clearInterval(this.riid);
					this.stop();
				}
			}, this.interval);
			return { result: true, medal: this.medal, pmedal: this.player.medal };
		} else { return { result: false, status: this.status }; }
	}
	stop() {
		if (this.status == this.STS.SPINING) {
			const medal = this.ROULETTE[this.ridx];
			this.medal -= medal;
			this.player.medal += medal;
			this.status = this.STS.STANDBY;
			this.onstop ? this.onstop() : null;
			return { result: true, medal: this.medal, pmedal: this.player.medal };
		} else { return { result: false, status: this.status }; }
	}
	insertMedal(player) {
		if (player.medal > 0 && this.medal > Math.max.apply(null,this.ROULETTE)) {
			player.medal--;
			this.medal++;
			return true;
		}
		return false;
	}
	judge(myHand, yourHand) { return this.JUDGE[myHand+yourHand]; }
	rnd(m) { return Math.floor(Math.random()*m); }
}

class JFView {
	constructor(cid, iid) {
		this.game = new JankenFever(100);
		this.game.onstart = ()=>this.drawComThinking();
		this.game.onwin = ()=>{
			clearInterval(this.plyItvID);
			this.canInsertMedal = true;
			this.blinkResult('win');
		};
		this.game.onlose = ()=>{
			clearInterval(this.plyItvID);
			this.canInsertMedal = true;
			this.blinkResult('lose');
			this.plyItvID = setTimeout(()=>this.drawDemo(), 4000);
		};
		this.game.ondraw = ()=>{
			clearInterval(this.plyItvID);
			this.blinkResult('draw');
			this.plyItvID = setTimeout(()=>this.drawComThinking(false), 1500);
		};
		this.game.onspin = (ri, rm) => this.drawRoulette(ri, rm);
		this.game.onstop = () => this.plyItvID = setTimeout(()=>this.drawDemo(), 3000);
		this.canvas = document.getElementById(cid);
		this.ctx = this.canvas.getContext('2d');
		this.image = document.getElementById(iid);
		this.canvas.width = 1024 * (window.innerWidth / window.innerHeight);
		this.canvas.height = 1024;
		this.canvas.style.width = window.innerWidth + 'px';
		this.canvas.style.height = window.innerHeight + 'px';
		this.wmgn = Math.floor((this.canvas.width - 1024) / 2);
		this.image.addEventListener('load', e=>this.drawInit());
		this.canInsertMedal = true;
		this.plyItvID;
		this.resItvID;
		this.myHand = '';
		this.cx = (canvas.width - 1024) / 2 + 536;
		this.cy = 411;
		this.r = 130;
		this.degs = [-76,-47,-15,17,49,77,102,131,163,-165,-133,-101];
		this.fs = 28;
		this.ctx.font = `bold ${this.fs}px Impact`;
		this.ctx.lineWidth = 2.5;
		this.canvas.addEventListener('click', (e) => {
			const cvsx = Math.floor(1024 * (e.x / window.innerHeight) - this.wmgn);
			const cvsy = Math.floor(1024 * (e.y / window.innerHeight));
			if (cvsx>440 && cvsx<615 && cvsy>705 && cvsy<925 && this.canInsertMedal) {
				clearInterval(this.plyItvID);
				this.game.start();
			}
			if (cvsx>355 && cvsx<441 && cvsy>586 && cvsy<632) this.onpress('R');
			if (cvsx>487 && cvsx<573 && cvsy>586 && cvsy<632) this.onpress('S');
			if (cvsx>621 && cvsx<707 && cvsy>586 && cvsy<632) this.onpress('P');
		});
	}
	drawInit() {
		const { ctx, image, wmgn } = this;
		ctx.drawImage(image, 0, 0, 1, 1024, 0, 0, wmgn + 1, 1024);
		ctx.drawImage(image, 1022, 0, 1, 1024, wmgn + 1023, 0, wmgn + 1, 1024);
		ctx.drawImage(image, 0, 0, 1024, 1024, wmgn, 0, 1024, 1024);
		this.drawDemo();
	}
	drawDemo() {
		this.drawComHand(0);
		this.plyItvID = setInterval((()=>{
			let rsp = 0;
			return () => this.drawComHand(rsp = (rsp + 1) % 3);
		})(), 1000);	
	}
	drawComHand(rsp) {
		const { ctx, image, wmgn, game } = this;
		const rsp_xy = [ {x:1024,y:0}, {x:1024,y:170}, {x:1024,y:340} ][rsp];
		ctx.drawImage(image, rsp_xy.x, rsp_xy.y, 150, 170, wmgn + 448, 317, 150, 170);
		this.drawRoulette(-1, game.ROULETTE);
	}
	drawRoulette(rt = -1, rtmst) {
		const { degs, ctx, cx, cy, r, fs } = this;
		let i = 0;
		rtmst.forEach((num)=>{
			const [numStr, rad] = [num + '', Math.PI / 180 * degs[i]];
			ctx.strokeStyle = rt===i ? '#FF0' : ['#C22','#C22'][i%2];
			ctx.fillStyle = rt===i ? '#F00' : ['#FFF','#FFC'][i%2];
			const dx = Math.floor(cx + r * Math.cos(rad) - (fs * numStr.length) / 2);
			const dy = Math.floor(cy + r * Math.sin(rad) * 185/210);
			ctx.strokeText(numStr, dx, dy);
			ctx.fillText(numStr, dx, dy);
			i++;
		});
	}
	onpress(rsp) {
		this.drawButton(rsp);
		if (!this.canInsertMedal) this.game.play(this.myHand = rsp);
	}
	drawButton(rsp) {
		const { ctx, image, wmgn } = this;
		const xy = {
			R: { px:1024, py:614, bx:355, by:586 },
			S: { px:1024, py:660, bx:487, by:586 },
			P: { px:1024, py:706, bx:621, by:586 }
		}[rsp];
		ctx.drawImage(image, xy.px, xy.py, 86, 46, wmgn + xy.bx, xy.by, 86, 46);
		setTimeout( () => ctx.drawImage(image, xy.bx, xy.by, 86, 46, wmgn + xy.bx, xy.by, 86, 46), 150);
	}
	drawComThinking(flush = true) {
		this.canInsertMedal = false;
		flush ? this.blinkOff() : null;
		let rsp = 0;
		this.plyItvID = setInterval(() => {
			rsp = ([0,1,2].filter(i=>i!==rsp))[Math.round(Math.random())];
			this.drawComHand(rsp);
		}, 100);
	}
	blinkOff() {
		const { resItvID, ctx, image, wmgn } = this;
		clearInterval(resItvID);
		ctx.drawImage(image, 343, 261, 68, 52, wmgn + 343, 261, 68, 52);
		ctx.drawImage(image, 646, 260, 68, 52, wmgn + 646, 260, 68, 52);
		ctx.drawImage(image, 343, 503, 68, 52, wmgn + 343, 503, 68, 52);
		ctx.drawImage(image, 646, 503, 68, 52, wmgn + 646, 503, 68, 52);
	}
	blinkResult(result) {
		const handSels = { win:{'R':1,'S':2,'P':0}, lose:{'R':2,'S':0,'P':1}, draw:{'R':0,'S':1,'P':2} };
		this.drawComHand(handSels[result][this.myHand]);
		this.blinkOff();
		const drawBlink = this.getDrawBlink(result);
		drawBlink();
		this.resItvID = setInterval(drawBlink, 500);
	}
	getDrawBlink(result) {
		const { wmgn, ctx, image } = this;
		const self = this;
		const p = [{
				winl:[1024, 510, 68, 52, wmgn + 343, 261, 68, 52],
				winr:[1092, 510, 68, 52, wmgn + 646, 260, 68, 52],
				lose:[1024, 562, 68, 52, wmgn + 343, 503, 68, 52],
				draw:[1092, 562, 68, 52, wmgn + 646, 503, 68, 52],
			},
			{
				winl:[343, 261, 68, 52, wmgn + 343, 261, 68, 52],
				winr:[646, 260, 68, 52, wmgn + 646, 260, 68, 52],
				lose:[343, 503, 68, 52, wmgn + 343, 503, 68, 52],
				draw:[646, 503, 68, 52, wmgn + 646, 503, 68, 52],
			}];
		let i = 0;
		return function() {
			i++;
			let d = p[i % 2][result === 'win' ? 'winl' : result];
			ctx.drawImage(image, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8]);
			if (result === 'win') {
				d = p[i % 2]['winr'];
				ctx.drawImage(image, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8]);
			}
			if (i === 7) clearInterval(self.resItvID);
		};
	}
}

const view = new JFView('canvas', 'base');

</script>
</body>

</html>

動かすとこんな感じ。

RPReplay_Final1714893101.gif

次回予告

  • メダル表示はどうしようね、考え中
  • 音声はVoiceVOXでずんだもんにお願い中
  • できあがったら自前のサイトにアップします(もちろんアフィリエイトを添えて笑)

追記:
https://qiita.com/tri-comma/items/53ba6d47e34e1d73d86d
 「 javascriptで作るゲーム【ジャンケンマンフィーバー】完結編(音声出力) 」
続きの記事を書きました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?