mirror of https://github.com/espruino/BangleApps
Add AccelaMaze game app
parent
9fc8d48852
commit
7e8df5593e
15
apps.json
15
apps.json
|
@ -5660,5 +5660,20 @@
|
|||
{"name":"timeandlife.app.js","url":"app.js"},
|
||||
{"name":"timeandlife.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "acmaze",
|
||||
"name": "AccelaMaze",
|
||||
"shortName":"AccelaMaze",
|
||||
"version":"0.01",
|
||||
"description": "Tilt the watch to roll a ball through a maze",
|
||||
"icon": "app.png",
|
||||
"tags": "game",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"storage": [
|
||||
{"name":"acmaze.app.js","url":"app.js"},
|
||||
{"name":"acmaze.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,17 @@
|
|||
# AccelaMaze
|
||||
|
||||
Tilt the watch to roll a ball through a maze.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
* Use the menu to select difficulty level (or exit).
|
||||
* Wait until the maze gets generated and a red ball appears.
|
||||
* Tilt the watch to get the ball into the green cell.
|
||||
|
||||
At any time you can click the button to return to the menu.
|
||||
|
||||
## Creator
|
||||
|
||||
[Nimrod Kerrett](https://zzzen.com)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwggaXh3M53/AA3yl4IHn//+EM5nMAoIX/C4RfCC4szmcxC4QFBAAUxC4UPAwIOB+YCCiMRkAFCkIGBAAQfBC4IUEAQhHIAAQX/C5EDmcyCgUTAoYXDR4kzC4UBPoKVB+YFFAQSPBiAKBiCnDGoZECABDUCa4YX/C5qPBQwoXGkczmC/FQYSSCVQSSCEwQOCC4hKFX4QXCd5YX/C4qMEmQXITAinDPoIADTwSPFkKMBX47RGI47XIC/4XCgZ9DQYYABmKYBmIXFkczmEBRIK/CQYQIBkECSoiSCA4MQa5pEFd6IX/RgMyC6H/QASVCRIS/EAQrXFJQoX/C6kDRQIXCiYFD+QFBmIUCkYFD+CJBiSPCRwIFFSoQFCiF3u9wI4gAO+wXW+IXygAAW"))
|
|
@ -0,0 +1,276 @@
|
|||
const MARGIN = 25;
|
||||
const WALL_RIGHT = 1, WALL_DOWN = 2;
|
||||
const STATUS_GENERATING = 0, STATUS_PLAYING = 1,
|
||||
STATUS_SOLVED = 2, STATUS_ABORTED = -1;
|
||||
|
||||
function Maze(n) {
|
||||
this.n = n;
|
||||
this.status = STATUS_GENERATING;
|
||||
this.wall_length = Math.floor((g.getHeight()-2*MARGIN)/n);
|
||||
this.total_length = this.wall_length*n;
|
||||
this.margin = Math.floor((g.getHeight()-this.total_length)/2);
|
||||
this.ball_x = 0;
|
||||
this.ball_y = 0;
|
||||
this.clearScreen = function() {
|
||||
g.clearRect(
|
||||
0, this.margin,
|
||||
g.getWidth(), this.margin+this.total_length
|
||||
);
|
||||
};
|
||||
this.clearScreen();
|
||||
g.setColor(g.theme.fg);
|
||||
for (let i=0; i<=n; i++) {
|
||||
g.drawRect(
|
||||
this.margin, this.margin+i*this.wall_length,
|
||||
g.getWidth()-this.margin, this.margin+i*this.wall_length
|
||||
);
|
||||
g.drawRect(
|
||||
this.margin+i*this.wall_length, this.margin,
|
||||
this.margin+i*this.wall_length, g.getHeight() - this.margin
|
||||
);
|
||||
}
|
||||
this.walls = new Uint8Array(n*n);
|
||||
this.groups = new Uint8Array(n*n);
|
||||
for (let cell = 0; cell<n*n; cell++) {
|
||||
this.walls[cell] = WALL_RIGHT|WALL_DOWN;
|
||||
this.groups[cell] = cell;
|
||||
}
|
||||
let from_group, to_group;
|
||||
let ngroups = n*n;
|
||||
while (--ngroups) {
|
||||
// Abort if BTN1 pressed [grace period for menu]
|
||||
// (for some reason setWatch() fails inside constructor)
|
||||
if (ngroups<n*n-4 && digitalRead(BTN1)) {
|
||||
aborting = true;
|
||||
return;
|
||||
}
|
||||
from_group = to_group = -1;
|
||||
while (from_group<0) {
|
||||
if (Math.random()<0.5) { // try to break a wall right
|
||||
let r = Math.floor(Math.random()*n);
|
||||
let c = Math.floor(Math.random()*(n-1));
|
||||
let cell = r*n+c;
|
||||
if (this.groups[cell]!=this.groups[cell+1]) {
|
||||
this.walls[cell] &= ~WALL_RIGHT;
|
||||
g.clearRect(
|
||||
this.margin+(c+1)*this.wall_length,
|
||||
this.margin+r*this.wall_length+1,
|
||||
this.margin+(c+1)*this.wall_length,
|
||||
this.margin+(r+1)*this.wall_length-1
|
||||
);
|
||||
g.flip(); // show progress.
|
||||
from_group = this.groups[cell];
|
||||
to_group = this.groups[cell+1];
|
||||
}
|
||||
} else { // try to break a wall down
|
||||
let r = Math.floor(Math.random()*(n-1));
|
||||
let c = Math.floor(Math.random()*n);
|
||||
let cell = r*n+c;
|
||||
if (this.groups[cell]!=this.groups[cell+n]) {
|
||||
this.walls[cell] &= ~WALL_DOWN;
|
||||
g.clearRect(
|
||||
this.margin+c*this.wall_length+1,
|
||||
this.margin+(r+1)*this.wall_length,
|
||||
this.margin+(c+1)*this.wall_length-1,
|
||||
this.margin+(r+1)*this.wall_length
|
||||
);
|
||||
from_group = this.groups[cell];
|
||||
to_group = this.groups[cell+n];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let cell = 0; cell<n*n; cell++) {
|
||||
if (this.groups[cell]==from_group) {
|
||||
this.groups[cell] = to_group;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.clearScreen = function() {
|
||||
g.clearRect(
|
||||
0, MARGIN, g.getWidth(), g.getHeight()-MARGIN-1
|
||||
);
|
||||
};
|
||||
this.clearCell = function(r, c) {
|
||||
if (!r && !c) {
|
||||
g.setColor("#ffff00");
|
||||
} else if (r==this.n-1 && c==this.n-1) {
|
||||
g.setColor("#00ff00");
|
||||
} else {
|
||||
g.setColor(g.theme.bg);
|
||||
}
|
||||
g.fillRect(
|
||||
this.margin+this.wall_length*c+1,
|
||||
this.margin+this.wall_length*r+1,
|
||||
this.margin+this.wall_length*(c+1),
|
||||
this.margin+this.wall_length*(r+1)
|
||||
);
|
||||
g.setColor(g.theme.fg);
|
||||
if (this.walls[r*n+c]&WALL_RIGHT) {
|
||||
g.fillRect(
|
||||
this.margin+this.wall_length*(c+1),
|
||||
this.margin+this.wall_length*r,
|
||||
this.margin+this.wall_length*(c+1),
|
||||
this.margin+this.wall_length*(r+1)
|
||||
);
|
||||
}
|
||||
if (this.walls[r*n+c]&WALL_DOWN) {
|
||||
g.fillRect(
|
||||
this.margin+this.wall_length*c,
|
||||
this.margin+this.wall_length*(r+1),
|
||||
this.margin+this.wall_length*(c+1),
|
||||
this.margin+this.wall_length*(r+1)
|
||||
);
|
||||
}
|
||||
};
|
||||
this.drawBall = function(x, y) {
|
||||
g.setColor("#ff0000");
|
||||
g.fillEllipse(
|
||||
this.margin+x+1,
|
||||
this.margin+y+1,
|
||||
this.margin+x+this.wall_length-1,
|
||||
this.margin+y+this.wall_length-1
|
||||
);
|
||||
g.setColor(g.theme.fg);
|
||||
};
|
||||
this.move = function(dx, dy) {
|
||||
let next_x = this.ball_x,
|
||||
next_y = this.ball_y,
|
||||
ball_r = Math.floor(this.ball_y/this.wall_length),
|
||||
ball_c = Math.floor(this.ball_x/this.wall_length);
|
||||
if (this.ball_x%this.wall_length) {
|
||||
if (dx) {
|
||||
next_x += dx;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (this.ball_y%this.wall_length) {
|
||||
if (dy) {
|
||||
next_y += dy;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else { // exactly in a cell. Check walls
|
||||
if (dy<0 && ball_r>0 && !(this.walls[n*(ball_r-1)+ball_c]&WALL_DOWN)) {
|
||||
next_y--;
|
||||
} else if (dy>0 && ball_r<(this.n-1) && !(this.walls[n*ball_r+ball_c]&WALL_DOWN)) {
|
||||
next_y++;
|
||||
} else if (dx<0 && ball_c>0 && !(this.walls[n*ball_r+ball_c-1]&WALL_RIGHT)) {
|
||||
next_x--;
|
||||
} else if (dx>0 && ball_c<(this.n-1) && !(this.walls[n*ball_r+ball_c]&WALL_RIGHT)) {
|
||||
next_x++;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.clearCell(ball_r, ball_c);
|
||||
if (this.ball_x%this.wall_length) {
|
||||
this.clearCell(ball_r, ball_c+1);
|
||||
}
|
||||
if (this.ball_y%this.wall_length) {
|
||||
this.clearCell(ball_r+1, ball_c);
|
||||
}
|
||||
this.ball_x = next_x;
|
||||
this.ball_y = next_y;
|
||||
this.drawBall(this.ball_x, this.ball_y);
|
||||
if (this.ball_x==(n-1)*this.wall_length && this.ball_y==(n-1)*this.wall_length) {
|
||||
this.status = STATUS_SOLVED;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
this.try_move_horizontally = function(accel_x) {
|
||||
if (accel_x>0.15) {
|
||||
return this.move(-1, 0);
|
||||
} else if (accel_x<-0.15) {
|
||||
return this.move(1, 0);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
this.try_move_vertically = function(accel_y) {
|
||||
if (accel_y<-0.15) {
|
||||
return this.move(0,1);
|
||||
} else if (accel_y>0.15) {
|
||||
return this.move(0,-1);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
this.tick = function() {
|
||||
accel = Bangle.getAccel();
|
||||
if (this.ball_x%this.wall_length) {
|
||||
this.try_move_horizontally(accel.x);
|
||||
} else if (this.ball_y%this.wall_length) {
|
||||
this.try_move_vertically(accel.y);
|
||||
} else {
|
||||
if (Math.abs(accel.x)>Math.abs(accel.y)) { // prefer horizontally
|
||||
if (!this.try_move_horizontally(accel.x)) {
|
||||
this.try_move_vertically(accel.y);
|
||||
}
|
||||
} else { // prefer vertically
|
||||
if (!this.try_move_vertically(accel.y)) {
|
||||
this.try_move_horizontally(accel.x);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
this.clearCell(0,0);
|
||||
this.clearCell(n-1,n-1);
|
||||
this.drawBall(0,0);
|
||||
this.status = STATUS_PLAYING;
|
||||
}
|
||||
|
||||
function timeToText(t) { // Courtesy of stopwatch app
|
||||
let hrs = Math.floor(t/3600000);
|
||||
let mins = Math.floor(t/60000)%60;
|
||||
let secs = Math.floor(t/1000)%60;
|
||||
let tnth = Math.floor(t/100)%10;
|
||||
let text;
|
||||
|
||||
if (hrs === 0)
|
||||
text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth;
|
||||
else
|
||||
text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
|
||||
return text;
|
||||
}
|
||||
|
||||
let aborting = false;
|
||||
let start_time = 0;
|
||||
let duration = 0;
|
||||
let maze=null;
|
||||
let mazeMenu = {
|
||||
"": { "title": "Maze size", "selected": 1 },
|
||||
"Easy (8x8)": function() { E.showMenu(); maze = new Maze(8); },
|
||||
"Medium (10x10)": function() { E.showMenu(); maze = new Maze(10); },
|
||||
"Hard (14x14)": function() { E.showMenu(); maze = new Maze(14); },
|
||||
"< Exit": function() { setTimeout(load, 100); } // timeout voodoo prevents deadlock
|
||||
};
|
||||
|
||||
g.clear(true);
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
Bangle.setLocked(false);
|
||||
Bangle.setLCDTimeout(0);
|
||||
E.showMenu(mazeMenu);
|
||||
let maze_interval = setInterval(
|
||||
function() {
|
||||
if (maze) {
|
||||
if (digitalRead(BTN1) || maze.status==STATUS_ABORTED) {
|
||||
console.log(`aborting ${start_time}`);
|
||||
maze = null;
|
||||
start_time = duration = 0;
|
||||
aborting = false;
|
||||
setTimeout(function() {E.showMenu(mazeMenu); }, 100);
|
||||
return;
|
||||
}
|
||||
if (!start_time) {
|
||||
start_time = Date.now();
|
||||
}
|
||||
if (maze.status==STATUS_PLAYING) {
|
||||
maze.tick();
|
||||
}
|
||||
if (maze.status==STATUS_SOLVED && !duration) {
|
||||
duration = Date.now()-start_time;
|
||||
g.setFontAlign(0,0).setColor(g.theme.fg);
|
||||
g.setFont("Vector",18);
|
||||
g.drawString(`Solved in\n ${timeToText(duration)} \nClick to play again`, g.getWidth()/2, g.getHeight()/2, true);
|
||||
}
|
||||
}
|
||||
}, 25);
|
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.8 KiB |
Loading…
Reference in New Issue