NEMTUSハッカソンに向けて
プログラム超初心者達が
Symbolを取り入れた簡単なゲームを開発するお話。
『~Challenge SymbolEnjoneer~』
一緒に見ていきましょう!Let’s Enjoy!
part.6
こんにちわ!サカショウです🐶
今回から、よりゲームらしいゲームの作成をします。
と言っても、全て細かく書き始めるとすごく大変になっちゃうので、僕がよく勉強に使っているところのご紹介もします。そちらも併せてトライして頂くとより学習効率があがると思いますよ!✨
それでは、僕と一緒に進めていきましょう。
※本連載記事に記載の内容は、テストネットのみの利用にとどめてください。 そのままメインネットに使用する事は、できません。 秘密鍵が公開されてしまいますので、絶対にメインネットで使用しないと約束してください。
【今回パートに必要なもの】
- PC(サカショウの環境は、Windows10)
- インターネット環境
- Symbol Desktop Wallet V1.0.9(テストネット)
- テストネットアカウント×2
- ブラウザ(Google chrome)(サカショウは、BraveのGoogleブラウザ)
- VSCode
()内は、サカショウの環境です。異なる環境の場合、異なる挙動を取る可能性があります。
【使用するツールとライブラリ】
- xembook / nem2-browserify / symbol-sdk-1.0.1.js
- FAUCET
- エクスプローラー
【Symbolスロット】
今界隈で話題の記事といえば、nem Advent Calendar 2021 5日目 ねむぐまさんのこの記事です!
今回のプログラミングチャレンジで学んできた事と通じる事なので、内容もすんなり入って来るのでは、ないでしょうか?是非、トライしてください🐶✨
【サカショウHack+Version】 スロット
【VSCodeを開いて作ってみよう!】
ねむぐまさんのAdventCalendar2021で掲載されたスロットゲームを基に 、作成しました!
今回サカモトが行った改造は、HTML、難易度調整、背景等style、ALL STOPボタン作成等、ちょこっと変更した程度です。が、印象がガラっと変わりました!
こちらも、VSCodeで新規フォルダーを作成し、 下記のコード群をコピペしてください。
サカショウVersionのブロック崩しV1 が複製出来ます。
このゲームもスロットゲームと同様に、遊ぶ為の変更ポイントは、2つだけです!
- XYMを送金するアカウントの秘密錠(0000000…の羅列記載箇所)
- XYMを受け取るアカウントのアドレス(aaaaaaa…の羅列記載の箇所)
上記2か所を自分のアカウント情報に変更することで、Symbolを実装したスロットゲームが遊べます🐶✨
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>NEMTUS Hack+2022 スロットゲーム🐶</title>
</head>
<body>
<h1>NEMTUS Hackathon Hack+2022</h1>
<p>
ねむぐまさんのAdventCalendar2021で掲載されたスロットゲームを基に作成しています。<br>
今回サカモトが行った改造は、HTML、難易度調整、style、ALL STOPボタン作成等ちょこっと変更した程度です。が、印象がガラっと変わりました!
</p>
<h3>暗号資産Symbol/XYM(テストネット)のトランザクション機能をスロットゲームに実装しています。</h3>
<p>
<ol type="I">
<li>スタートボタンを押してください。</li>
<li>リールが回転し始めます。</li>
<li>左リールからSTOPボタンを押してリールを停止させて下さい。</li>
<h4>【左リール0~3/中リール0~2/右リール0~2】</h4>
<li>右リール停止時に数字が揃っていれば、Txに署名されSymbolネットワークにアナウンス、XYMが送金されます。</li>
</ol>
</p>
<!-- 設定値の定義とライブラリのインポート&ライブラリのインスタンス生成 -->
<script src="https://xembook.github.io/nem2-browserify/symbol-sdk-1.0.1.js"></script>
<script language="JavaScript">
NODE = 'https://sym-test-09.opening-line.jp:3001';
GENERATION_HASH = '7FCCD304802016BEBBCD342A332F91FF1F3BB5E902988B352697BE245F48E836';
EPOCH_ADJUSTMENT = 1637848847;
nem = require("/node_modules/symbol-sdk");
var time1;
var time2;
var time3;
// START
function slot() {
start1();
start2();
start3();
}
//各ランダム値を表示(スロットリール内に表示する数字と表示切替速度)
function start1() {
document.getElementById("dat1").value = Math.floor(Math.random() * 3);
time1 = setTimeout(start1, 9);
}
function start2() {
document.getElementById("dat2").value = Math.floor(Math.random() * 2);
time2 = setTimeout(start2, 9);
}
function start3() {
document.getElementById("dat3").value = Math.floor(Math.random() * 2);
time3 = setTimeout(start3, 9);
}
// STOP
function stop1() {
clearTimeout(time1);
}
function stop2() {
clearTimeout(time2);
}
function stop3() {
clearTimeout(time3);
const textbox = document.getElementById("dat1");
const inputValue1 = textbox.value;
console.log(inputValue1);
const textbox2 = document.getElementById("dat2");
const inputValue2 = textbox2.value;
console.log(inputValue2);
const textbox3 = document.getElementById("dat3");
const inputValue3 = textbox3.value;
console.log(inputValue3);
if (inputValue1 === inputValue2 && inputValue3 === inputValue1) {
//ゲームの払い出し設定
//XYMを送金するアカウントの秘密錠に変更する。(0000000...の羅列記載箇)
alice = nem.Account.createFromPrivateKey('00000000000000000000000000000000000000000000000000000000000000000', nem.NetworkType.TEST_NET);
console.log(alice);
//プレーヤー設定
// XYMを受け取るアカウントのアドレスに変更する。(aaaaaaa...の羅列記載の箇所)
tx = nem.TransferTransaction.create(
nem.Deadline.create(EPOCH_ADJUSTMENT),
nem.Address.createFromRawAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
[new nem.Mosaic(new nem.MosaicId('3A8416DB2D53B6C8'), nem.UInt64.fromUint(1000000))],
nem.PlainMessage.create('NEMTUS Hackathon Hack+2022 スロットwin'),
nem.NetworkType.TEST_NET,
nem.UInt64.fromUint(100000)
);
console.log(tx);
// 署名
signedTx = alice.sign(tx, GENERATION_HASH);
console.log(signedTx);
// 署名したトランザクションをネットワークにアナウンス
new nem.TransactionHttp(NODE)
.announce(signedTx)
.subscribe((x) => console.log(x), (err) => console.error(err));
alert('Yaaaattaaa!X(^Y^)M');
}
else {
console.log('残念...もう1回!!');
}
}
</script>
<style>
table,
tr,
td {
border-style: none;
}
</style>
//背景は、ここのあたりCollarコードを探すと見つかるよ!
<div style="background-color : #7586bb; padding : 20px; width:1135px;">
<table>
<tr>
<td colspan="3">
<input type="button" value="START" onClick="slot()" style="width:100%;">
</td>
</tr>
<tr>
<td>
<input type="text" id="dat1" value="0" style="text-align:center;font-size:30px">
</td>
<td>
<input type="text" id="dat2" value="0" style="text-align:center;font-size:30px">
</td>
<td>
<input type="text" id="dat3" value="0" style="text-align:center;font-size:30px">
</td>
</tr>
//All Stopボタン作成(button作る→名前つける→ワンクリックでどの順番に実行するか?→サイズ)
<tr>
<td colspan="3">
<input type="button" value="ALL STOP" onClick="stop1()>stop2()>stop3()" style="width:100%;">
</td>
</tr>
<tr>
<td>
<input type="button" value="STOP" onClick="stop1()" style="width:100%;">
</td>
<td>
<input type="button" value="STOP" onClick="stop2()" style="width:100%;">
</td>
<td>
<input type="button" value="STOP" onClick="stop3()" style="width:100%;">
</td>
</tr>
</table>
</div>
</script>
</body>
</html>
【ブロック崩し Ⅴ1】
まずは、上記リンクのチュートリアルにトライして、ブロック崩しゲームを完成させましょう!
チュートリアルを繰り返し学習することで、Javascript初級~中級レベルぐらいの知識を身に着けられると思います🐶
【サカショウHack+Version】 ブロック崩しV1
サカショウHack+Versionでは、全てのブロックが消された時に、Symbol$XYMのトランザクションがネットワークにアナウンスされるように設定しています。
【VSCodeを開いて作ってみよう!】
こちらも、VSCodeで新規フォルダーを作成し、 下記のコード群をコピペしてください。
サカショウVersionのブロック崩しV1 が複製出来ます。
このゲームもスロットゲームと同様に、遊ぶ為の変更ポイントは、2つだけです!
- XYMを送金するアカウントの秘密錠(0000000…の羅列記載箇所)
- XYMを受け取るアカウントのアドレス(aaaaaaa…の羅列記載の箇所)
上記2か所を自分のアカウント情報に変更することで、Symbolを実装したブロック崩しゲームが遊べます🐶✨
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>ブロック崩しV1</title>
<style>
* { padding: 0; margin: 0; }
canvas { background: #eee; display: block; margin: 0 auto;
</style>
</head>
<body>
<canvas id="myCanvas" width="480" height="320"></canvas>
<script src="https://xembook.github.io/nem2-browserify/symbol-sdk-1.0.1.js"></script>
<script language="JavaScript">
NODE = 'https://sym-test-07.opening-line.jp:3001';
GENERATION_HASH = '7FCCD304802016BEBBCD342A332F91FF1F3BB5E902988B352697BE245F48E836';
EPOCH_ADJUSTMENT = 1637848847;
nem = require("/node_modules/symbol-sdk");
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var ballRadius = 10;
var x = canvas.width/2;
var y = canvas.height-30;
var dx = 2;
var dy = -2;
var paddleHeight = 10;
var paddleWidth = 75;
var paddleX = (canvas.width-paddleWidth)/2;
var rightPressed = false;
var leftPressed = false;
var brickRowCount = 5;
var brickColumnCount = 3;
var brickWidth = 75;
var brickHeight = 20;
var brickPadding = 10;
var brickOffsetTop = 30;
var brickOffsetLeft = 30;
var score = 0;
var lives = 1;
var bricks = [];
for(var c=0; c<brickColumnCount; c++) {
bricks[c] = [];
for(var r=0; r<brickRowCount; r++) {
bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
document.addEventListener("mousemove", mouseMoveHandler, false);
function keyDownHandler(e) {
if(e.key == "Right" || e.key == "ArrowRight") {
rightPressed = true;
}
else if(e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = true;
}
}
function keyUpHandler(e) {
if(e.key == "Right" || e.key == "ArrowRight") {
rightPressed = false;
}
else if(e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = false;
}
}
function mouseMoveHandler(e) {
var relativeX = e.clientX - canvas.offsetLeft;
if(relativeX > 0 && relativeX < canvas.width) {
paddleX = relativeX - paddleWidth/2;
}
}
function collisionDetection() {
for(var c=0; c<brickColumnCount; c++) {
for(var r=0; r<brickRowCount; r++) {
var b = bricks[c][r];
if(b.status == 1) {
if(x > b.x && x < b.x+brickWidth && y > b.y && y < b.y+brickHeight) {
dy = -dy;
b.status = 0;
score++;
if(score == brickRowCount*brickColumnCount) {
alert("YOU WIN, CONGRATS!");
//XYMを送金するアカウントの秘密錠に変更する。(0000000...の羅列記載箇)
alice = nem.Account.createFromPrivateKey("00000000000000000000000000000000000000000000000000000000000000000",
nem.NetworkType.TEST_NET);
console.log(alice);
// XYMを受け取るアカウントのアドレスに変更する。(aaaaaaa...の羅列記載の箇所)
tx = nem.TransferTransaction.create(
nem.Deadline.create(EPOCH_ADJUSTMENT),
nem.Address.createFromRawAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
[new nem.Mosaic(new nem.MosaicId('3A8416DB2D53B6C8'), nem.UInt64.fromUint(10000000))],
nem.PlainMessage.create('NEMTUS Hackathon Hack+2022 ブロック崩しClear!'),
nem.NetworkType.TEST_NET,
nem.UInt64.fromUint(100000)
);
console.log(tx);
signedTx = alice.sign(tx, GENERATION_HASH);
console.log(signedTx);
new nem.TransactionHttp(NODE)
.announce(signedTx)
.subscribe((x) => console.log(x), (err) => console.error(err));
}
}
}
}
}
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, canvas.height-paddleHeight, paddleWidth, paddleHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function drawBricks() {
for(var c=0; c<brickColumnCount; c++) {
for(var r=0; r<brickRowCount; r++) {
if(bricks[c][r].status == 1) {
var brickX = (r*(brickWidth+brickPadding))+brickOffsetLeft;
var brickY = (c*(brickHeight+brickPadding))+brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
}
}
}
function drawScore() {
ctx.font = "16px Arial";
ctx.fillStyle = "#0095DD";
ctx.fillText("Score: "+score, 8, 20);
}
function drawLives() {
ctx.font = "16px Arial";
ctx.fillStyle = "#0095DD";
ctx.fillText("Lives: "+lives, canvas.width-65, 20);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBricks();
drawBall();
drawPaddle();
drawScore();
drawLives();
collisionDetection();
if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if(y + dy < ballRadius) {
dy = -dy;
}
else if(y + dy > canvas.height-ballRadius) {
if(x > paddleX && x < paddleX + paddleWidth) {
dy = -dy;
}
else {
lives--;
if(!lives) {
alert("GAME OVER");
document.location.reload();
}
else {
x = canvas.width/2;
y = canvas.height-30;
dx = 3;
dy = -3;
paddleX = (canvas.width-paddleWidth)/2;
}
}
}
if(rightPressed && paddleX < canvas.width-paddleWidth) {
paddleX += 7;
}
else if(leftPressed && paddleX > 0) {
paddleX -= 7;
}
x += dx;
y += dy;
requestAnimationFrame(draw);
}
draw();
</script>
</body>
</html>
【終わりに…】
うまく動作しましたか?
次回Part7では、ブロック崩しゲームV1に対して、いくつか変更を加えて別ステージを作ってみたいと思います!🐶✨