penetrateModeLimitCount: 3,
var life = SETTING_VALUES.life;
var collapsedTileCount = 0;
var lifeIndicator = null;
var settingPanelGraphics = null;
collide: function(ball) {
if (ball.x - ball.r < 0) ball.reflectX();
if (ball.x + ball.r >= canvas.width) ball.reflectX();
if (ball.y - ball.r < 0) ball.reflectY();
if (ball.y - ball.r >= canvas.height) {
speed: SETTING_VALUES.ballSpeed,
penetrateModeLimitCount: SETTING_VALUES.penetrateModeLimitCount,
ball.x += this.dx * this.speed;
ball.y += this.dy * this.speed;
reflectBar: function(barX, barW) {
decrementCount: function() {
if (this.penetrateModeLimitCount > 0) {
this.penetrateModeLimitCount--;
this.disablePenetrateMode();
disablePenetrateMode: function() {
this.penetrateMode = false;
enablePenetrateMode: function() {
this.penetrateModeLimitCount = SETTING_VALUES.penetrateModeLimitCount;
this.penetrateMode = true;
if (this.speed < VALID_VALUES.maxBallSpeed) {
reflectAngle: function(x, y) {
if (this.penetrateMode) {
circle(this.x, this.y, this.r * 2);
x: function() { return mouseX - this.w / 2; },
collide: function(ball) {
if (ball.y + ball.r >= this.y &&
ball.y - ball.r <= this.y &&
ball.x + ball.r >= this.x() &&
ball.x - ball.r <= this.x() + this.w) {
ball.reflectBar(ball.x - mouseX, (this.w / 10 * 4));
this.x() + (this.w / 2), this.y + 1,
this.x() + (this.w / 2), this.y + this.h - 1
if (this.w < VALID_VALUES.maxBarWidth) {
this.w += this.incrementValue;
if (this.w > VALID_VALUES.minBarWidth) {
this.w -= this.incrementValue;
var canvasImages = new Array(SETTING_VALUES.numOfImages);
for(let index = 0 ; index < canvasImages.length ; index++) {
canvasImages[index] = loadImage(`canvas_image_${index}.png`);
if (initialValueValidation()) {
canvasImages[SETTING_VALUES.numOfImages-1].width,
canvasImages[SETTING_VALUES.numOfImages-1].height
settingPanelGraphics = createGraphics(canvas.width-20, canvas.height-20);
var tileSize = Math.floor(canvas.width / SETTING_VALUES.tileResolution);
tiles = new Array(Math.floor(canvas.width / tileSize));
items = new Array(Math.floor(canvas.width / tileSize));
life = SETTING_VALUES.life;
lifeIndicator = new LifeIndicator(VALID_VALUES.maxLife, life);
for (var i = 0; i < tiles.length; i++) {
tiles[i] = new Array(Math.floor((canvas.height - 60) / tileSize));
items[i] = new Array(Math.floor((canvas.height - 60) / tileSize));
for (var j = 0; j < tiles[i].length; j++) {
tiles[i][j] = new Tile(i * tileSize, j * tileSize, tileSize, tileSize);
tiles[i][j].isBorder = SETTING_VALUES.drawTileBorder;
Math.floor(Math.random() * SETTING_VALUES.itemAppearanceRate) < 1 &&
SETTING_VALUES.isEnableItem
items[i][j] = new Item(tiles[i][j]);
items[i][j].setItemType(Math.floor(Math.random() * SETTING_VALUES.numOfItemTypes) + 1);
if (tiles[i][j].opaque == true) {
lifeIndicator.decrement();
for (var i = 0; i < tiles.length; i++) {
for (var j = 0; j < tiles[i].length; j++) {
bar.y = canvas.height - 30;
bar.w = SETTING_VALUES.barWidth;
ball.x = canvas.width / 2;
ball.speed = SETTING_VALUES.ballSpeed;
life = SETTING_VALUES.life;
settingPanel.openable = true;
settingPanel.isOpen = false;
for (var i = 0; i < tiles.length; i++) {
for (var j = 0; j < tiles[i].length; j++) {
bar.y = canvas.height - 30;
ball.x = canvas.width / 2;
for (var i = 0; i < tiles.length; i++) {
for (var j = 0; j < tiles[i].length; j++) {
bar.y = canvas.height - 30;
ball.x = canvas.width / 2;
const ERROR_MESSAGE = "invalid initial value";
maxPenetrateModeLimitCount: 5,
maxItemAppearanceRate: 30,
minItemAppearanceRate: 15
function initialValueValidation() {
isValid &&= (SETTING_VALUES.life > 0 && SETTING_VALUES.life <= VALID_VALUES.maxLife);
SETTING_VALUES.penetrateModeLimitCount > 0 &&
SETTING_VALUES.penetrateModeLimitCount <= VALID_VALUES.maxPenetrateModeLimitCount
SETTING_VALUES.ballSpeed > 0 &&
SETTING_VALUES.ballSpeed <= VALID_VALUES.maxBallSpeed
SETTING_VALUES.barWidth > VALID_VALUES.minBarWidth &&
SETTING_VALUES.barWidth <= VALID_VALUES.maxBarWidth
SETTING_VALUES.tileResolution >= VALID_VALUES.minTileResolution &&
SETTING_VALUES.tileResolution <= VALID_VALUES.maxTileResolution
SETTING_VALUES.itemAppearanceRate >= VALID_VALUES.minItemAppearanceRate &&
SETTING_VALUES.itemAppearanceRate <= VALID_VALUES.maxItemAppearanceRate
if (isValid) return true;
window.alert(ERROR_MESSAGE);
if (settingPanel.isOpen && settingPanel.openable) {
settingPanel.drawSettingPanel();
for (var j = 0; j < tiles.length; j++) {
for (var i = 0; i < tiles[j].length; i++) {
for (var j = 0; j < items.length; j++) {
for (var i = 0; i < items[j].length; i++) {
if (items[j][i] != null) {
tiles[j][i].item = items[j][i];
settingPanel.drawSettingButton();
function drawBackground() {
background(canvasImages[SETTING_VALUES.numOfImages - 1]);
for (let i = SETTING_VALUES.numOfImages - 2 ; i > stageCount ; i--) {
image(canvasImages[i], 0, 0);
function drawOverTile() {
if (!isComplete && !isNext) {
overTileImage = canvasImages[stageCount].get(
canvasImages[stageCount].height - 80,
canvasImages[stageCount].width,
image(overTileImage, 0, canvasImages[stageCount].height - 80);
function withIn(x, y, width, height) {
function mousePressed() {
if (settingPanel.openable &&
withIn(canvas.width - 30, 10, 20, 20)
settingPanel.toggleOpen();
if (settingPanel.isOpen) {
isUpdated ||= settingPanel.drawTileBorderToggleButton.onClick();
isUpdated ||= settingPanel.enableItemToggleButton.onClick();
isUpdated ||= settingPanel.lifeSpinButton.increment();
isUpdated ||= settingPanel.lifeSpinButton.decrement();
isUpdated ||= settingPanel.ballSpeedSpinButton.increment();
isUpdated ||= settingPanel.ballSpeedSpinButton.decrement();
isUpdated ||= settingPanel.barWidthSpinButton.increment();
isUpdated ||= settingPanel.barWidthSpinButton.decrement();
isUpdated ||= settingPanel.tileResolutionSpinButton.increment();
isUpdated ||= settingPanel.tileResolutionSpinButton.decrement();
isUpdated ||= settingPanel.itemAppearanceRateSpinButton.increment();
isUpdated ||= settingPanel.itemAppearanceRateSpinButton.decrement();
isUpdated ||= settingPanel.penetrateModeLimitCountSpinButton.increment();
isUpdated ||= settingPanel.penetrateModeLimitCountSpinButton.decrement();
settingPanel.openable = false;
if (window.confirm(`ステージ${stageCount+2}へ進む?`)) {
if (window.confirm("リセット?")) {
SETTING_VALUES.drawTileBorder = settingPanel.drawTileBorderToggleButton.isEnabled;
SETTING_VALUES.isEnableItem = settingPanel.enableItemToggleButton.isEnabled;
settingPanel.itemAppearanceRateSpinButton.isEnable =
settingPanel.enableItemToggleButton.isEnabled;
settingPanel.penetrateModeLimitCountSpinButton.isEnable =
settingPanel.enableItemToggleButton.isEnabled;
SETTING_VALUES.life = settingPanel.lifeSpinButton.value;
SETTING_VALUES.ballSpeed = settingPanel.ballSpeedSpinButton.value;
SETTING_VALUES.barWidth = settingPanel.barWidthSpinButton.value;
SETTING_VALUES.tileResolution = settingPanel.tileResolutionSpinButton.value;
SETTING_VALUES.itemAppearanceRate = settingPanel.itemAppearanceRateSpinButton.value;
SETTING_VALUES.penetrateModeLimitCount = settingPanel.penetrateModeLimitCountSpinButton.value;
bar.w = SETTING_VALUES.barWidth;
ball.speed = SETTING_VALUES.ballSpeed;
ball.penetrateModeLimitCount = SETTING_VALUES.penetrateModeLimitCount;
life = SETTING_VALUES.life;
constructor(x, y, w, h) {
this.bottomY = y + h / 2;
this.cropTile = canvasImages[stageCount].get(this.x, this.y, this.w, this.h);
this.cropTile.loadPixels();
let pixelCount = this.cropTile.width * this.cropTile.height * 4;
for (let i = 0; i < pixelCount; i += 4) {
sumAlpha += this.cropTile.pixels[i + 3];
if (this.opaque && this.alive) {
if (ball.y + ball.r >= this.y &&
ball.y - ball.r <= this.y + this.h &&
ball.x + ball.r >= this.x &&
ball.x - ball.r <= this.x + this.w) {
if (!ball.penetrateMode) {
ball.dy *= (ball.y <= this.y || ball.y >= this.y + this.h) ? -1 : 1;
ball.dx *= (ball.x <= this.x || ball.x >= this.x + this.w) ? -1 : 1;
image(this.cropTile, this.x, this.y, this.w, this.h);
if (collapsedTileCount >= totalTileCount * 0.85) {
if (stageCount + 2 < SETTING_VALUES.numOfImages) {
this.y += 1 * this.speed;
this.x - this.size, this.y + this.size,
this.x + this.size, this.y + this.size
this.x, this.y - this.size,
this.x - this.size, this.y,
this.x + this.size, this.y
this.x, this.y + this.size,
this.x - this.size, this.y,
this.x + this.size, this.y
this.x - this.size, this.y - this.size,
this.x + this.size, this.y - this.size
circle(this.x, this.y, this.size * 2);
this.x - this.size, this.y,
this.x - 2, this.y + this.size,
this.x - 2, this.y - this.size
this.x + this.size, this.y,
this.x + 2, this.y - this.size,
this.x + 2, this.y + this.size
this.x - this.size, this.y + this.size,
this.x - this.size, this.y - this.size
this.x + this.size, this.y - this.size,
this.x + this.size, this.y + this.size
this.x, this.y + this.size,
this.x - this.size, this.y,
this.x + this.size, this.y
this.x - (this.size / 2), this.y,
this.x + (this.size / 2), this.y,
ball.enablePenetrateMode();
lifeIndicator.increment();
if (!this.locked && this.alive) {
if (this.y + this.size >= bar.y &&
this.y - this.size <= bar.y &&
this.x + this.size >= bar.x() &&
this.x - this.size <= bar.x() + bar.w) {
constructor(maxLife, initLife) {
this.iconList = new Array(maxLife);
this.initLife = initLife;
for (let idx = 0 ; idx < this.iconList.length ; idx++) {
this.iconList[idx] = new LifeIcon(idx);
this.iconList[idx].disableDraw();
this.iconList[idx].draw();
if (this.life < this.maxLife) {
this.iconList[this.life].enableDraw();
this.iconList[this.life++].draw();
this.iconList[--this.life].disableDraw();
this.iconList[this.life].draw();
this.life = this.initLife;
for(let idx = 0 ; idx < this.iconList.length ; idx++) {
if (idx < this.life) this.iconList[idx].enableDraw();
else this.iconList[idx].disableDraw();
this.iconList[idx].draw();
for(let i = 0 ; i < this.life ; i++) {
this.x = lifeX * 50 + 30;
enableDraw() { this.opaque = true; }
disableDraw() { this.opaque = false; }
this.x, this.y + this.size,
this.x - this.size, this.y,
this.x + this.size, this.y
this.x - (this.size / 2), this.y,
this.x + (this.size / 2), this.y,
constructor(label, x, y, isEnabled) {
this.isEnabled = isEnabled;
this.height = this.width / 2;
this.label = new Label(label, this.width + 20, this.y);
if (this.withInButtonArea()) {
this.isEnabled = !this.isEnabled;
return withIn(this.x + 10, this.y + 10, this.width, this.height);
settingPanelGraphics.fill(50, 200, 50);
settingPanelGraphics.fill(150, 150, 150);
settingPanelGraphics.noStroke();
settingPanelGraphics.rect(
settingPanelGraphics.fill(250, 250, 250);
settingPanelGraphics.circle(
this.x + this.height + (this.height / 2),
this.y + (this.height / 2),
settingPanelGraphics.circle(
this.x + (this.height / 2),
this.y + (this.height / 2),
this.label.isEnable = this.isEnabled;
constructor(label, x, y, initialValue, min, max, step) {
this.value = initialValue;
this.height = this.width / 3;
this.label = new Label(label, this.width + 20, this.y);
x: this.x + (this.height * 2.5),
this.withInIncrementArea() &&
this.value <= this.max - this.step
this.withInDecrementArea() &&
this.value >= this.min + this.step
this.incrementArea.x + 10,
this.incrementArea.y + 10,
this.incrementArea.width,
this.incrementArea.height
this.decrementArea.x + 10,
this.decrementArea.y + 10,
this.decrementArea.width,
this.decrementArea.height
settingPanelGraphics.fill(250);
settingPanelGraphics.fill(150);
this.setFillColor(this.value > this.min && this.isEnable);
settingPanelGraphics.triangle(
this.x, this.y + (this.height / 2),
this.x + (this.height / 2), this.y,
this.x + (this.height / 2), this.y + this.height
this.setFillColor(this.value < this.max && this.isEnable);
settingPanelGraphics.triangle(
this.x + (this.height * 3), this.y + (this.height / 2),
this.x + (this.height * 2.5), this.y,
this.x + (this.height * 2.5), this.y + this.height
this.setFillColor(this.isEnable);
settingPanelGraphics.noStroke();
settingPanelGraphics.textSize(this.height);
settingPanelGraphics.textAlign(CENTER, CENTER);
settingPanelGraphics.text(
this.x + (this.height * 1.5),
this.y + (this.height / 2)
this.label.isEnable = this.isEnable;
constructor(text, x, y) {
settingPanelGraphics.fill(250);
settingPanelGraphics.fill(150);
this.setFillColor(this.isEnable);
settingPanelGraphics.noStroke();
settingPanelGraphics.textSize(this.size);
settingPanelGraphics.textAlign(LEFT, CENTER);
settingPanelGraphics.text(this.text, this.x, this.y);
drawTileBorderToggleButton: new ToggleButton("ブロックの輪郭表示", 10, 40, true),
enableItemToggleButton: new ToggleButton("ドロップアイテム有効", 10, 80, true),
lifeSpinButton: new SpinButton("残機", 10, 120, 3, 1, 10, 1),
ballSpeedSpinButton: new SpinButton("ボールスピード", 10, 160, 6, 1, 10, 1),
barWidthSpinButton: new SpinButton("反射バーの幅", 10, 200, 100, 40, 200, 20),
tileResolutionSpinButton: new SpinButton("ブロック密度", 10, 240, 30, 30, 50, 10),
itemAppearanceRateSpinButton:
new SpinButton("アイテムドロップ率(1/設定値)", 10, 280, 20, 15, 30, 1),
penetrateModeLimitCountSpinButton:
new SpinButton("貫通モードの持続する反射回数", 10, 320, 3, 1, 5, 1),
drawSettingButton: function() {
circle(canvas.width - 20, 20, 25);
textAlign(CENTER, CENTER);
text("⛭", canvas.width - 20, 20);
drawSettingPanel: function() {
settingPanelGraphics.fill(50);
settingPanelGraphics.rect(
settingPanelGraphics.width,
settingPanelGraphics.height
this.drawTileBorderToggleButton.draw();
this.enableItemToggleButton.draw();
this.lifeSpinButton.draw();
this.ballSpeedSpinButton.draw();
this.barWidthSpinButton.draw();
this.tileResolutionSpinButton.draw();
this.itemAppearanceRateSpinButton.draw();
this.penetrateModeLimitCountSpinButton.draw();
settingPanelGraphics.fill(255);
settingPanelGraphics.stroke(255);
settingPanelGraphics.textSize(20);
settingPanelGraphics.textAlign(CENTER, CENTER);
settingPanelGraphics.text("✖", settingPanelGraphics.width - 10, 10);
image(settingPanelGraphics, 10, 10);
this.isOpen = !this.isOpen;