Merge branch 'master' into binary-clock

master
Gordon Williams 2019-11-13 10:26:07 +00:00 committed by GitHub
commit defdf30393
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 579 additions and 0 deletions

View File

@ -93,6 +93,17 @@
{"name":"*clickms","url":"click-master-icon.js","evaluate":true} {"name":"*clickms","url":"click-master-icon.js","evaluate":true}
] ]
}, },
{ "id": "horsey",
"name": "Horse Race!",
"icon": "horse-race.png",
"description": "Get several friends to start the game, then compete to see who can press BTN1 the most!",
"tags": "game",
"storage": [
{"name":"+horsey","url":"horse-race.json"},
{"name":"-horsey","url":"horse-race.js"},
{"name":"*horsey","url":"horse-race-icon.js","evaluate":true}
]
},
{ "id": "compass", { "id": "compass",
"name": "Compass", "name": "Compass",
"icon": "compass.png", "icon": "compass.png",
@ -373,6 +384,18 @@
{"name":"*hrings","url":"hypno-rings-icon.js","evaluate":true} {"name":"*hrings","url":"hypno-rings-icon.js","evaluate":true}
] ]
}, },
{ "id": "morse",
"name": "Morse Code",
"icon": "morse-code.png",
"description": "Learn morse code by hearing/seeing/feeling the code. Tap to toggle buzz!",
"tags": "morse,sound,visual,input",
"type":"app",
"storage": [
{"name":"+morse","url":"morse-code.json"},
{"name":"-morse","url":"morse-code.js"},
{"name":"*morse","url":"morse-code-icon.js","evaluate":true}
]
},
{ {
"id": "blescan", "id": "blescan",
"name": "BLE Scanner", "name": "BLE Scanner",
@ -431,5 +454,17 @@
{"name":"-bclock","url":"clock-binary.js"}, {"name":"-bclock","url":"clock-binary.js"},
{"name":"*bclock","url":"clock-binary-icon.js","evaluate":true} {"name":"*bclock","url":"clock-binary-icon.js","evaluate":true}
] ]
},
{ "id": "clotris",
"name": "Clock-Tris",
"icon": "clock-tris.png",
"description": "A fully functional clone of a classic game of falling blocks",
"tags": "",
"storage": [
{"name":"+clotris","url":"clock-tris.json"},
{"name":"-clotris","url":"clock-tris.js"},
{"name":"*clotris","url":"clock-tris-icon.js","evaluate":true},
{"name":".trishig","url":"clock-tris-high"}
]
} }
] ]

1
apps/clock-tris-high Normal file
View File

@ -0,0 +1 @@
0

1
apps/clock-tris-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwiBC/AH4A/AHOQzYBNJ/5f/L/5P/L/5f/J/5f/L/5P/L/4A/AH6/haP5f/L/5f/L/5f/L/5f/L/5f/L/4A/AFP/ABy/lL/5f/L/5f/L/5f/L/5f/L/5f/L/4A/VtK/jL/5f/L/5f/L/5f/L/5f/L/5f/L/4A/X/7RxEa5f/L/5f/L/5f/L/5f/L/5f/L/5ffAH4A/AHYA="))

309
apps/clock-tris.js Normal file
View File

@ -0,0 +1,309 @@
Bangle.setLCDMode("doublebuffered");
const storage = require("Storage");
var BTN_L = BTN1;
var BTN_R = BTN3;
var BTN_ROT = BTN2;
var BTN_DOWN = BTN5;
var BTN_PAUSE = BTN4;
const W = g.getWidth();
const H = g.getHeight();
const CX = W / 2;
const CY = H / 2;
const HEIGHT_BUFFER = 4;
const LINES = 20;
const COLUMNS = 11;
const CELL_SIZE = Math.floor((H - HEIGHT_BUFFER) / (LINES + 1));
const BOARD_X = Math.floor((W - CELL_SIZE * COLUMNS) / 2) + 2;
const BOARD_Y = Math.floor((H - CELL_SIZE * (LINES + 1)) / 2);
const BOARD_W = COLUMNS * CELL_SIZE;
const BOARD_H = LINES * CELL_SIZE;
const TEXT_X = BOARD_X + BOARD_W + 10;
const BLOCKS = [
[
[2, 7],
[2, 6, 2],
[0, 7, 2],
[2, 3, 2]
],
[
[1, 3, 2],
[6, 3]
],
[
[2, 3, 1],
[3, 6]
],
[
[2, 2, 6],
[0, 7, 1],
[3, 2, 2],
[4, 7]
],
[
[2, 2, 3],
[1, 7],
[6, 2, 2],
[0, 7, 4]
],
[
[2, 2, 2, 2],
[0, 15]
],
[[3, 3]]
];
const COLOR_WHITE = 0b1111111111111111;
const COLOR_BLACK = 0b0000000000000000;
const BLOCK_COLORS = [
//0brrrrrggggggbbbbb
0b0111100000001111,
0b0000011111100000,
0b1111100000000011,
0b0111100111100000,
0b0000000000011111,
0b0000001111111111,
0b1111111111100000
];
const EMPTY_LINE = 0b00000000000000;
const BOUNDARY = 0b10000000000010;
const FULL_LINE = 0b01111111111100;
let gameOver = false;
let paused = false;
let currentBlock = 0;
let nextBlock = 0;
let x, y;
let points;
let level;
let lines;
let board;
let rotation = 0;
let ticker = null;
let needDraw = true;
let highScore = parseInt(storage.read(".trishig") || 0, 10);
function getBlock(a, c, d) {
const block = BLOCKS[a % 7];
return block[(a + c) % block.length];
}
function drawBlock(block, screenX, screenY, x, y) {
for (let row in block) {
let mask = block[row];
for (let col = 0; mask; mask >>= 1, col++) {
if (mask % 2) {
const dx = screenX + (x + col) * CELL_SIZE;
const dy = screenY + (y + row) * CELL_SIZE;
g.fillRect(dx, dy, dx + CELL_SIZE - 3, dy + CELL_SIZE - 3);
}
}
}
}
function drawBoard() {
g.setColor(COLOR_WHITE);
g.drawRect(BOARD_X - 3, BOARD_Y - 3, BOARD_X + BOARD_W, BOARD_Y + BOARD_H);
drawBlock(board, BOARD_X, BOARD_Y, -2, 0);
g.setColor(BLOCK_COLORS[currentBlock]);
drawBlock(getBlock(currentBlock, rotation), BOARD_X, BOARD_Y, x - 2, y);
}
function drawNextBlock() {
g.setFontAlign(0, -1, 0);
g.setColor(COLOR_WHITE);
g.drawString("NEXT BLOCK", BOARD_X / 2, 10);
g.setColor(BLOCK_COLORS[nextBlock]);
drawBlock(getBlock(nextBlock, 0), BOARD_X / 2 - 2 * CELL_SIZE, 25, 0, 0);
}
function drawTextLine(text, line) {
g.drawString(text, TEXT_X, 10 + line * 15);
}
function drawGameState() {
g.setFontAlign(-1, -1, 0);
g.setColor(COLOR_WHITE);
let ln = 0;
drawTextLine("CLOCK-TRIS", ln++);
ln++;
drawTextLine("LVL " + level, ln++);
drawTextLine("LNS " + lines, ln++);
drawTextLine("PTS " + points, ln++);
drawTextLine("TOP " + highScore, ln++);
}
function drawBanner(text) {
g.setFontAlign(0, 0, 0);
g.setColor(COLOR_BLACK);
g.fillRect(CX - 46, CY - 11, CX + 46, CY + 9);
g.setColor(COLOR_WHITE);
g.drawRect(CX - 45, CY - 10, CX + 45, CY + 8);
g.drawString(text, CX, CY);
}
function drawPaused() {
drawBanner("PAUSED");
}
function drawGameOver() {
drawBanner("GAME OVER");
}
function draw() {
g.clear();
g.setFont("6x8");
drawBoard();
drawNextBlock();
drawGameState();
if (paused) {
drawPaused();
}
if (gameOver) {
drawGameOver();
}
g.flip();
}
function getNextBlock() {
currentBlock = nextBlock;
nextBlock = (Math.random() * BLOCKS.length) | 0;
x = 6;
y = 0;
rotation = 0;
}
function landBlock(a) {
const block = getBlock(currentBlock, rotation);
for (let row in block) {
board[y + (row | 0)] |= block[row] << x;
}
let clearedLines = 0;
let keepLine = LINES;
for (let line = LINES - 1; line >= 0; line--) {
if (board[line] === FULL_LINE) {
clearedLines++;
} else {
board[--keepLine] = board[line];
}
}
lines += clearedLines;
if (lines > level * 10) {
level++;
setSpeed();
}
while (--keepLine > 0) {
board[keepLine] = EMPTY_LINE;
}
if (clearedLines) {
points += 100 * (1 << (clearedLines - 1));
needDraw = true;
}
getNextBlock();
if (!checkMove(0, 0, 0)) {
gameOver = true;
needDraw = true;
highScore = Math.max(points, highScore);
storage.write(".trishig", highScore.toString());
}
}
function checkMove(dx, dy, rot) {
if (gameOver) {
startGame();
return;
}
if (paused) {
return;
}
const block = getBlock(currentBlock, rotation + rot);
for (const row in block) {
const movedBlockRow = block[row] << (x + dx);
if (
row + y === LINES - 1 ||
movedBlockRow & board[y + dy + row] ||
movedBlockRow & BOUNDARY
) {
if (dy) {
landBlock();
}
return false;
}
}
rotation += rot;
x += dx;
y += dy;
needDraw = true;
return true;
}
function drawLoop() {
if (needDraw) {
needDraw = false;
draw();
}
setTimeout(drawLoop, 10);
}
function gameTick() {
if (!gameOver) {
checkMove(0, 1, 0);
}
}
function setSpeed() {
if (ticker) {
clearInterval(ticker);
}
ticker = setInterval(gameTick, 1000 - level * 100);
}
function togglePause() {
if (!gameOver) {
paused = !paused;
needDraw = true;
}
}
function startGame() {
board = [];
for (let i = 0; i < LINES; i++) {
board[i] = EMPTY_LINE;
}
gameOver = false;
points = 0;
lines = 0;
level = 0;
getNextBlock();
setSpeed();
needDraw = true;
}
function bindButton(btn, dx, dy, r) {
setWatch(checkMove.bind(null, dx, dy, r), btn, { repeat: true });
}
bindButton(BTN_L, -1, 0, 0);
bindButton(BTN_R, 1, 0, 0);
bindButton(BTN_ROT, 0, 0, 1);
bindButton(BTN_DOWN, 0, 1, 0);
setWatch(togglePause, BTN_PAUSE, { repeat: true });
startGame();
drawLoop();

5
apps/clock-tris.json Normal file
View File

@ -0,0 +1,5 @@
{
"name":"Clock-Tris",
"icon":"*clotris",
"src":"-clotris"
}

BIN
apps/clock-tris.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

1
apps/horse-race-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AVvNPp95F1tPqujF9IuCvPO5wspAAXHF84uFp4uBL84xEvPN5q+rqtiCBl6F7tiAAY9LvQDBF86cDvQvCGLYvEGAr7EF4IwDF7c4GQwuERwIACqpecrlcF4lWLw4ACF7s3F58rGDIvEA4VcFwtWL4ovSCwwvHFoiOHGCQXHXYdcBwQuIDIwsMI5QwEGIKNERgh6JFpIuKAAOAGIYvCqpYJAwUyFxaNIGAovEXBheOF5pfCrl6RYjoTVAYvMRwYOKF76NDwAveGBaNEF8AwLFzgvHeRovoqtWFxtVDQbwSF44KBqouLDYzAYqz8OPg5gSD6K9FGCIvJVoIdLdoxgUDop9NF7gcEPZwvZJgwvnbiwTHF54bLMCEsAAIvVTRBEOF7zBSF7StPGDB1HPpyMVDAgZFbxztWAH4A/AGw"))

70
apps/horse-race.js Normal file
View File

@ -0,0 +1,70 @@
Bangle.setLCDMode("doublebuffered");
var img = require("Storage").read("*horsey");
var mycounter = 0;
var players = {};
setWatch(x=>{
mycounter++;
updateAdvertising();
},BTN1,{repeat:true});
function updateAdvertising() {
try {
NRF.setAdvertising({},{
manufacturer: 0x0590,
manufacturerData: new Uint8Array([mycounter>>8,mycounter&255]),
interval: 60
});
} catch(e){}
}
function drawPlayers() {
g.clear(1);
g.setBgColor(0,0.7,0);
g.setFont("6x8",2);
g.setFontAlign(0,0,1);
var max = mycounter;
for (var player of players) {
max = Math.max(player.cnt, mycounter);
}
var offset = 0;
if (max > 220)
offset = max-220;
var d = 63 - (offset&63);
g.fillRect(0,10,240,12);
for (var x=d;x<240;x+=64)
g.fillRect(x,12,x+2,12+20);
var y = 20;
g.drawImage(img, mycounter-offset,y);
for (var player of players) {
y+=45;
g.drawString(player.name,10,y);
g.drawImage(img, player.cnt-offset,20);
}
g.fillRect(0,150,240,152);
for (var x=d;x<240;x+=64)
g.fillRect(x,152,x+2,160);
g.flip();
}
function doScan() {
NRF.findDevices(devs=>{
devs.forEach(dev => {
players[dev.id] = {
name : dev.id.substr(12,5),
cnt : (dev.manufacturerData[0]<<8)|dev.manufacturerData[1]
};
});
drawPlayers();
doScan();
},{timeout : 250, filters : [{ manufacturerData:{0x0590:{}} }] });
}
drawPlayers();
try { NRF.wake(); } catch (e) {}
doScan();
setInterval(drawPlayers, 100);
updateAdvertising();

5
apps/horse-race.json Normal file
View File

@ -0,0 +1,5 @@
{
"name":"Horse Race",
"icon": "*horser",
"src":"-horser"
}

BIN
apps/horse-race.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

147
apps/morse-code.js Normal file
View File

@ -0,0 +1,147 @@
/**
* Teach a user morse code
*/
/**
* Constants
*/
const FONT_NAME = 'Vector12';
const FONT_SIZE = 80;
const SCREEN_PIXELS = 240;
const UNIT = 100;
const MORSE_MAP = {
A: '.-',
B: '-...',
C: '-.-.',
D: '-..',
E: '.',
F: '..-.',
G: '--.',
H: '....',
I: '..',
J: '.---',
K: '-.-',
L: '.-..',
M: '--',
N: '-.',
O: '---',
P: '.--.',
Q: '--.-',
R: '.-.',
S: '...',
T: '-',
U: '..-',
V: '...-',
W: '.--',
X: '-..-',
Y: '-.--',
Z: '--..',
'1': '.----',
'2': '..---',
'3': '...--',
'4': '....-',
'5': '.....',
'6': '-....',
'7': '--...',
'8': '---..',
'9': '----.',
'0': '-----',
};
/**
* Set the local state
*/
let INDEX = 0;
let BEEPING = false;
let BUZZING = true;
let UNIT_INDEX = 0;
let UNITS = MORSE_MAP[Object.keys(MORSE_MAP)[INDEX]].split('');
/**
* Utility functions for writing text, changing state
*/
const writeText = (txt) => {
g.clear();
const width = g.stringWidth(txt);
g.drawString(txt, (SCREEN_PIXELS / 2) - (width / 2), SCREEN_PIXELS / 2);
};
const writeLetter = () => {
writeText(Object.keys(MORSE_MAP)[INDEX]);
};
const writeCode = () => {
writeText(MORSE_MAP[Object.keys(MORSE_MAP)[INDEX]]);
};
const setUnits = () => {
UNITS = MORSE_MAP[Object.keys(MORSE_MAP)[INDEX]].split('');
};
/**
* Bootstrapping
*/
g.clear();
g.setFont(FONT_NAME, FONT_SIZE);
g.setColor(0, 1, 0);
g.setFontAlign(-1, 0, 0);
/**
* The length of a dot is one unit
* The length of a dash is three units
* The length of a space is one unit
* The space between letters is three units
* The space between words is seven units
*/
const beepItOut = () => {
// If we are starting the beeps, use a timeout for pause of three units
const wait = UNIT_INDEX === 0 ? UNIT * 3 : 0;
setTimeout(() => {
Promise.all([
Bangle.beep(UNITS[UNIT_INDEX] === '.' ? UNIT : 3 * UNIT),
// Could make buzz optional or switchable potentially
BUZZING ? Bangle.buzz(UNITS[UNIT_INDEX] === '.' ? UNIT : 3 * UNIT) : null
])
.then(() => {
if (UNITS[UNIT_INDEX + 1]) {
setTimeout(() => {
UNIT_INDEX++;
beepItOut();
}, UNIT);
} else {
setTimeout(() => {
BEEPING = false;
UNIT_INDEX = 0;
writeLetter();
}, 3 * UNIT);
}
});
}, wait);
};
const startBeep = () => {
if (BEEPING) return;
else {
BEEPING = true;
writeCode();
beepItOut();
}
};
const step = (positive) => () => {
if (BEEPING) return;
if (positive) {
INDEX = INDEX + 1;
if (INDEX > Object.keys(MORSE_MAP).length - 1) INDEX = 0;
} else {
INDEX = INDEX - 1;
if (INDEX < 0) INDEX = Object.keys(MORSE_MAP).length - 1;
}
setUnits();
writeLetter();
};
const toggleBuzzing = () => (BUZZING = !BUZZING);
writeLetter();
// Press the middle button to hear the morse code translation
setWatch(startBeep, BTN2, { repeat: true });
// Allow user to switch between letters
setWatch(step(true), BTN1, { repeat: true });
setWatch(step(false), BTN3, { repeat: true });
// Toggle buzzing/beeping with the touchscreen
setWatch(toggleBuzzing, BTN4, { repeat: true });
setWatch(toggleBuzzing, BTN5, { repeat: true });

5
apps/morse-code.json Normal file
View File

@ -0,0 +1,5 @@
{
"name":"Morse Code","type":"app",
"icon":"*morse",
"src":"-morse"
}

BIN
apps/morse-code.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B