forked from FOSS/BangleApps
Initial commit of BuffGym 5x5 training program
parent
e052a6e65f
commit
74bd784b09
14
apps.json
14
apps.json
|
@ -1293,5 +1293,19 @@
|
|||
"evaluate": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "buffgym",
|
||||
"name": "BuffGym",
|
||||
"icon": "buffgym.png",
|
||||
"version":"0.01",
|
||||
"description": "BuffGym is the famous 5x5 workout program for the BangleJS",
|
||||
"tags": "tool,outdoors",
|
||||
"type": "app",
|
||||
"storage": [
|
||||
{"name":"buffgym"},
|
||||
{"name":"buffgym.app.js"},
|
||||
{"name":"buffgym.img","url":"buffgym-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
2
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"windows"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
const STARTED = 1;
|
||||
const RESTING = 2;
|
||||
const COMPLETED = 3;
|
||||
const ONE_SECOND = 1000;
|
||||
const INCREMENT = "increment";
|
||||
const DECREMENT = "decrement";
|
||||
|
||||
class Exercise {
|
||||
constructor(params /*{title, weight, unit, restPeriod}*/) {
|
||||
const DEFAULTS = {
|
||||
title: "Unknown",
|
||||
weight: 0,
|
||||
unit: "Kg",
|
||||
restPeriod: 90,
|
||||
weightIncrement: 2.5,
|
||||
};
|
||||
const p = Object.assign({}, DEFAULTS, params);
|
||||
|
||||
this._title = p.title;
|
||||
this._weight = p.weight;
|
||||
this._unit = p.unit;
|
||||
this._originalRestPeriod = p.restPeriod; // Used when reseting _restPeriod
|
||||
this._restPeriod = p.restPeriod;
|
||||
this._weightIncrement = p.weightIncrement;
|
||||
this._started = new Date();
|
||||
this._completed = false;
|
||||
this._sets = [];
|
||||
this._restTimeout = null;
|
||||
this._restInterval = null;
|
||||
this._state = null;
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
get humanTitle() {
|
||||
return `${this._title} ${this._weight}${this._unit}`;
|
||||
}
|
||||
|
||||
get subTitle() {
|
||||
const totalSets = this._sets.length;
|
||||
const uncompletedSets = this._sets.filter((set) => !set.isCompleted()).length;
|
||||
const currentSet = (totalSets - uncompletedSets) + 1;
|
||||
return `Set ${currentSet} of ${totalSets}`;
|
||||
}
|
||||
|
||||
get restPeriod() {
|
||||
return this._restPeriod;
|
||||
}
|
||||
|
||||
decRestPeriod() {
|
||||
this._restPeriod--;
|
||||
}
|
||||
|
||||
get weight() {
|
||||
return this._weight;
|
||||
}
|
||||
|
||||
get unit() {
|
||||
return this._unit;
|
||||
}
|
||||
|
||||
get started() {
|
||||
return this._started;
|
||||
}
|
||||
|
||||
addSet(set) {
|
||||
this._sets.push(set);
|
||||
}
|
||||
|
||||
addSets(sets) {
|
||||
sets.forEach(set => this.addSet(set));
|
||||
}
|
||||
|
||||
get currentSet() {
|
||||
return this._sets.filter(set => !set.isCompleted())[0];
|
||||
}
|
||||
|
||||
isLastSet() {
|
||||
return this._sets.filter(set => !set.isCompleted()).length === 1;
|
||||
}
|
||||
|
||||
isCompleted() {
|
||||
return !!this._completed;
|
||||
}
|
||||
|
||||
canSetCompleted() {
|
||||
return this._sets.filter(set => set.isCompleted()).length === this._sets.length;
|
||||
}
|
||||
|
||||
setCompleted() {
|
||||
if (!this.canSetCompleted()) throw "All sets must be completed";
|
||||
if (this.canProgress) this._weight += this._weightIncrement;
|
||||
this._completed = true;
|
||||
}
|
||||
|
||||
get canProgress() {
|
||||
let completedRepsTotalSum = 0;
|
||||
let targetRepsTotalSum = 0;
|
||||
|
||||
const completedRepsTotal = this._sets.forEach(set => completedRepsTotalSum += set.reps);
|
||||
const targetRepsTotal = this._sets.forEach(set => targetRepsTotalSum += set.maxReps);
|
||||
|
||||
return (targetRepsTotalSum - completedRepsTotalSum) === 0;
|
||||
}
|
||||
|
||||
startRestTimer(program) {
|
||||
this._restTimeout = setTimeout(() => {
|
||||
this.next();
|
||||
}, ONE_SECOND * this._restPeriod);
|
||||
|
||||
this._restInterval = setInterval(() => {
|
||||
program.emit("redraw");
|
||||
}, ONE_SECOND);
|
||||
}
|
||||
|
||||
resetRestTimer() {
|
||||
clearTimeout(this._restTimeout);
|
||||
clearInterval(this._restInterval);
|
||||
this._restTimeout = null;
|
||||
this._restInterval = null;
|
||||
this._restPeriod = this._originalRestPeriod;
|
||||
}
|
||||
|
||||
isRestTimerRunning() {
|
||||
return this._restTimeout != null;
|
||||
}
|
||||
|
||||
setupStartedButtons(program) {
|
||||
clearWatch();
|
||||
|
||||
setWatch(() => {
|
||||
this.currentSet.incReps();
|
||||
program.emit("redraw");
|
||||
}, BTN1, {repeat: true});
|
||||
|
||||
setWatch(program.next.bind(program), BTN2, {repeat: false});
|
||||
|
||||
setWatch(() => {
|
||||
this.currentSet.decReps();
|
||||
program.emit("redraw");
|
||||
}, BTN3, {repeat: true});
|
||||
}
|
||||
|
||||
setupRestingButtons(program) {
|
||||
clearWatch();
|
||||
setWatch(program.next.bind(program), BTN2, {repeat: true});
|
||||
}
|
||||
|
||||
next(program) {
|
||||
global.poo = this;
|
||||
switch(this._state) {
|
||||
case null:
|
||||
console.log("XXX 1 moving null -> STARTED");
|
||||
this._state = STARTED;
|
||||
this.setupStartedButtons(program);
|
||||
break;
|
||||
case STARTED:
|
||||
console.log("XXX 2 moving STARTED -> RESTING");
|
||||
this._state = RESTING;
|
||||
this.startRestTimer(program);
|
||||
this.setupRestingButtons(program);
|
||||
break;
|
||||
case RESTING:
|
||||
this.resetRestTimer();
|
||||
this.currentSet.setCompleted();
|
||||
|
||||
if (this.canSetCompleted()) {
|
||||
console.log("XXX 3b moving RESTING -> COMPLETED");
|
||||
this._state = COMPLETED;
|
||||
this.setCompleted();
|
||||
} else {
|
||||
console.log("XXX 3a moving RESTING -> null");
|
||||
this._state = null;
|
||||
}
|
||||
// As we are changing state and require it to be reprocessed
|
||||
// invoke the next step of program
|
||||
program.next(program);
|
||||
break;
|
||||
default:
|
||||
throw "Exercise: Attempting to move to an unknown state";
|
||||
}
|
||||
|
||||
program.emit("redraw");
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AFEEolAC6lN7vdDCcECwPd6guVGCYuDC4cCBQMikQXQJAMjkECmcyIx4XDmUjmYvLC4XUDARHBIoIWLgATCGQdA7tEonQC5ouDDYg0BOxgSEAggwKRwgUCC6ZIDSwoXNogWDDgNCAgIWIkUEoUk6kiCgMkokipsiBIQXIki2CAgNCAoYADC5Eic4Mic4ICCAIIJCC5MzAAcykYGEAAIXOABAXTmUzGoIXVAIIXLB4SICDIovjO76PZbYR3PDI4XiI6530MIh3SC6R33C/oAOC48CCxsgC44A/ADY="))
|
|
@ -0,0 +1,68 @@
|
|||
class Program {
|
||||
constructor(params) {
|
||||
const DEFAULTS = {
|
||||
title: "Unknown",
|
||||
trainDay: "", // Day of week
|
||||
};
|
||||
const p = Object.assign({}, DEFAULTS, params);
|
||||
|
||||
this._title = p.title;
|
||||
this._trainDay = p.trainDay;
|
||||
this._exercises = [];
|
||||
|
||||
this.on("redraw", redraw.bind(null, this));
|
||||
}
|
||||
|
||||
get title() {
|
||||
return `${this._title} - ${this._trainDay}`;
|
||||
}
|
||||
|
||||
addExercise(exercise) {
|
||||
this._exercises.push(exercise);
|
||||
}
|
||||
|
||||
addExercises(exercises) {
|
||||
exercises.forEach(exercise => this.addExercise(exercise));
|
||||
}
|
||||
|
||||
currentExercise() {
|
||||
return (
|
||||
this._exercises
|
||||
.filter(exercise => !exercise.isCompleted())[0]
|
||||
);
|
||||
}
|
||||
|
||||
canComplete() {
|
||||
return (
|
||||
this._exercises
|
||||
.filter(exercise => exercise.isCompleted())
|
||||
.length === this._exercises.length
|
||||
);
|
||||
}
|
||||
|
||||
setCompleted() {
|
||||
if (!this.canComplete()) throw "All exercises must be completed";
|
||||
this._completed = true;
|
||||
}
|
||||
|
||||
isCompleted() {
|
||||
return !!this._completed;
|
||||
}
|
||||
|
||||
// State machine
|
||||
next() {
|
||||
console.log("XXX Program.next");
|
||||
const exercise = this.currentExercise();
|
||||
|
||||
// All exercises are completed so mark the
|
||||
// Program as comleted
|
||||
if (this.canComplete()) {
|
||||
this.setCompleted();
|
||||
this.emit("redraw");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
exercise.next(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
class Set {
|
||||
constructor(maxReps) {
|
||||
this._minReps = 0;
|
||||
this._maxReps = maxReps;
|
||||
this._reps = 0;
|
||||
this._completed = false;
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
get weight() {
|
||||
return this._weight;
|
||||
}
|
||||
|
||||
isCompleted() {
|
||||
return !!this._completed;
|
||||
}
|
||||
|
||||
setCompleted() {
|
||||
this._completed = true;
|
||||
}
|
||||
|
||||
get reps() {
|
||||
return this._reps;
|
||||
}
|
||||
|
||||
get maxReps() {
|
||||
return this._maxReps;
|
||||
}
|
||||
|
||||
incReps() {
|
||||
if (this._completed) return;
|
||||
if (this._reps >= this._maxReps) return;
|
||||
|
||||
this._reps++;
|
||||
}
|
||||
|
||||
decReps() {
|
||||
if (this._completed) return;
|
||||
if (this._reps <= this._minReps) return;
|
||||
|
||||
this._reps--;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,311 @@
|
|||
/* global g, setWatch, clearWatch, reset, BTN1, BTN2, BTN3 */
|
||||
|
||||
(() => {
|
||||
const W = g.getWidth();
|
||||
const H = g.getHeight();
|
||||
const RED = "#d32e29";
|
||||
const PINK = "#f05a56";
|
||||
const WHITE = "#ffffff";
|
||||
|
||||
const Set = require("set.js");
|
||||
const Exercise = require("exercise.js");
|
||||
const Program = require("program.js");
|
||||
|
||||
function centerStringX(str) {
|
||||
return (W - g.stringWidth(str)) / 2;
|
||||
}
|
||||
|
||||
function iconIncrement() {
|
||||
const img = require("heatshrink").decompress(atob("ikUxH+AA3XAAgNHCJIVMBYXQ5PC4XJ6AUJCIQQBAAoVCCQwjCAA/JCgglHA4IpJBYwTHA4RMJCY5oDJo4THKIQKET5IMGCaY7TMaKLTWajbTFJIlICgoVBFYXJYQYSGCggAGCRAVIBgw"));
|
||||
return img;
|
||||
}
|
||||
|
||||
function iconDecrement() {
|
||||
const img = require("heatshrink").decompress(atob("ikUxH+AA3XAAgNHCJIVMBYXQ5PC4XJ6AUJCIQQBAAoVCCQwjCAA/JCgglKFJADBCRYABCYQmOFAhNMKIw6FTw4LHCaY7TMaKLTWajbTFJglFCgoVBFYXJYQYSGCggAGCRAVIBgw="));
|
||||
return img;
|
||||
}
|
||||
|
||||
function iconOk() {
|
||||
const img = require("heatshrink").decompress(atob("ikUxH+AA3XAAgNHCJIVMBYXQ5PC4XJ6AUJCIQQBAAoVCCQwjCAA/JCgglKFJADBCJQxCCYQmMIwZoDJpQMCKIg6KBYwTGFQgeHHYouCCRI7EMYTXFRhILEK5SfFRgYSIborbSbpglFCgoVBFYXJYQYSGCggAGCRAVIBgwA=="));
|
||||
return img;
|
||||
}
|
||||
|
||||
function drawMenu(params) {
|
||||
const DEFAULT_PARAMS = {
|
||||
showBTN1: false,
|
||||
showBTN2: false,
|
||||
showBTN3: false,
|
||||
};
|
||||
const p = Object.assign({}, DEFAULT_PARAMS, params);
|
||||
if (p.showBTN1) g.drawImage(iconIncrement(), W - 30, 10);
|
||||
if (p.showBTN2) g.drawImage(iconOk(), W - 30, 110);
|
||||
if (p.showBTN3) g.drawImage(iconDecrement(), W - 30, 210);
|
||||
}
|
||||
|
||||
function clearScreen() {
|
||||
g.setColor(RED);
|
||||
g.fillRect(0,0,W,H);
|
||||
}
|
||||
|
||||
function drawTitle(exercise) {
|
||||
const title = exercise.humanTitle;
|
||||
|
||||
g.setFont("Vector",20);
|
||||
g.setColor(WHITE);
|
||||
g.drawString(title, centerStringX(title), 5);
|
||||
}
|
||||
|
||||
function drawReps(exercise) {
|
||||
const set = exercise.currentSet;
|
||||
if (set.isCompleted()) return;
|
||||
|
||||
g.setColor(PINK);
|
||||
g.fillCircle(W / 2, H / 2, 50);
|
||||
g.setColor(WHITE);
|
||||
g.setFont("Vector", 40);
|
||||
g.drawString(set.reps, centerStringX(set.reps), (H - 45) / 2);
|
||||
g.setFont("Vector", 15);
|
||||
const note = `of ${set.maxReps}`;
|
||||
g.drawString(note, centerStringX(note), (H / 2) + 25);
|
||||
}
|
||||
|
||||
function drawSets(exercise) {
|
||||
const sets = exercise.subTitle;
|
||||
|
||||
g.setColor(WHITE);
|
||||
g.setFont("Vector", 15);
|
||||
g.drawString(sets, centerStringX(sets), H - 25);
|
||||
}
|
||||
|
||||
function drawSetProgress(exercise) {
|
||||
drawTitle(exercise);
|
||||
drawReps(exercise);
|
||||
drawSets(exercise);
|
||||
drawMenu({showBTN1: true, showBTN2: true, showBTN3: true});
|
||||
}
|
||||
|
||||
function drawStartNextExercise() {
|
||||
const title = "Good work";
|
||||
const msg = "No need to rest\nmove straight on\nto the next exercise";
|
||||
|
||||
g.setColor(WHITE);
|
||||
g.setFont("Vector", 35);
|
||||
g.drawString(title, centerStringX(title), 10);
|
||||
g.setFont("Vector", 15);
|
||||
g.drawString(msg, 30, 150);
|
||||
drawMenu({showBTN1: false, showBTN2: true, showBTN3: false});
|
||||
}
|
||||
|
||||
function drawProgramCompleted() {
|
||||
const title1 = "You did";
|
||||
const title2 = "GREAT!";
|
||||
const msg = "That's the program\ncompleted. Now eat\nsome food and\nget plenty of rest.";
|
||||
|
||||
clearWatch();
|
||||
setWatch(reset, BTN2, {repeat: false});
|
||||
|
||||
g.setColor(WHITE);
|
||||
g.setFont("Vector", 35);
|
||||
g.drawString(title1, centerStringX(title1), 10);
|
||||
g.setFont("Vector", 40);
|
||||
g.drawString(title2, centerStringX(title2), 50);
|
||||
g.setFont("Vector", 15);
|
||||
g.drawString(msg, 30, 150);
|
||||
drawMenu({showBTN1: false, showBTN2: true, showBTN3: false});
|
||||
}
|
||||
|
||||
/*
|
||||
function drawExerciseCompleted(program) {
|
||||
const exercise = program.currentExercise();
|
||||
const title = exercise.canProgress?
|
||||
"WELL DONE!" :
|
||||
"NOT BAD!";
|
||||
const msg = exercise.canProgress?
|
||||
`You weight is automatically increased\nfor ${exercise.title} to ${exercise.weight}${exercise.unit}` :
|
||||
"It looks like you struggled\non a few sets, your weight will\nstay the same";
|
||||
const action = "Move straight on to the next exercise";
|
||||
|
||||
clearScreen();
|
||||
g.setColor(WHITE);
|
||||
g.setFont("Vector", 20);
|
||||
g.drawString(title, centerStringX(title), 10);
|
||||
g.setFont("Vector", 10);
|
||||
g.drawString(msg, centerStringX(msg), 180);
|
||||
g.drawString(action, centerStringX(action), 210);
|
||||
drawMenu({showBTN2: true});
|
||||
|
||||
clearWatch();
|
||||
setWatch(() => {
|
||||
init(program);
|
||||
}, BTN2, {repeat: false});
|
||||
}
|
||||
*/
|
||||
|
||||
function drawRestTimer(program) {
|
||||
const exercise = program.currentExercise();
|
||||
const motivation = "Take a breather..";
|
||||
clearScreen();
|
||||
drawMenu({showBTN2: true});
|
||||
|
||||
g.setColor(PINK);
|
||||
g.fillCircle(W / 2, H / 2, 50);
|
||||
g.setColor(WHITE);
|
||||
g.setFont("Vector", 15);
|
||||
g.drawString(motivation, centerStringX(motivation), 25);
|
||||
g.setFont("Vector", 40);
|
||||
g.drawString(exercise.restPeriod, centerStringX(exercise.restPeriod), (H - 45) / 2);
|
||||
exercise.decRestPeriod();
|
||||
|
||||
if (exercise.restPeriod <= 0) {
|
||||
exercise.resetRestTimer();
|
||||
redraw(program);
|
||||
}
|
||||
}
|
||||
|
||||
function redraw(program) {
|
||||
const exercise = program.currentExercise();
|
||||
|
||||
clearScreen();
|
||||
|
||||
if (program.isCompleted()) {
|
||||
drawProgramCompleted(program);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exercise.isRestTimerRunning()) {
|
||||
if (exercise.isLastSet()) {
|
||||
drawStartNextExercise(program);
|
||||
} else {
|
||||
drawRestTimer(program);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
drawSetProgress(exercise);
|
||||
}
|
||||
|
||||
function init(program) {
|
||||
clearWatch();
|
||||
program.next();
|
||||
}
|
||||
|
||||
// Setup training program. This should come from file
|
||||
|
||||
// Squats
|
||||
function buildPrograms() {
|
||||
const squats = new Exercise({
|
||||
title: "Squats",
|
||||
weight: 40,
|
||||
unit: "Kg",
|
||||
});
|
||||
squats.addSets([
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
]);
|
||||
|
||||
const bench = new Exercise({
|
||||
title: "Bench press",
|
||||
weight: 20,
|
||||
unit: "Kg",
|
||||
});
|
||||
bench.addSets([
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
]);
|
||||
|
||||
const row = new Exercise({
|
||||
title: "Row",
|
||||
weight: 20,
|
||||
unit: "Kg",
|
||||
});
|
||||
row.addSets([
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
]);
|
||||
|
||||
const ohPress = new Exercise({
|
||||
title: "Overhead press",
|
||||
weight: 20,
|
||||
unit: "Kg",
|
||||
});
|
||||
ohPress.addSets([
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
new Set(5),
|
||||
]);
|
||||
|
||||
const deadlift = new Exercise({
|
||||
title: "Deadlift",
|
||||
weight: 20,
|
||||
unit: "Kg",
|
||||
});
|
||||
deadlift.addSets([
|
||||
new Set(5),
|
||||
]);
|
||||
|
||||
const pullups = new Exercise({
|
||||
title: "Pullups",
|
||||
weight: 0,
|
||||
unit: "Kg",
|
||||
});
|
||||
pullups.addSets([
|
||||
new Set(10),
|
||||
new Set(10),
|
||||
new Set(10),
|
||||
]);
|
||||
|
||||
const triceps = new Exercise({
|
||||
title: "Tricep extension",
|
||||
weight: 20,
|
||||
unit: "Kg",
|
||||
});
|
||||
triceps.addSets([
|
||||
new Set(10),
|
||||
new Set(10),
|
||||
new Set(10),
|
||||
]);
|
||||
|
||||
const programA = new Program({
|
||||
title: "Program A",
|
||||
});
|
||||
programA.addExercises([
|
||||
squats,
|
||||
ohPress,
|
||||
deadlift,
|
||||
pullups,
|
||||
]);
|
||||
|
||||
const programB = new Program({
|
||||
title: "Program B",
|
||||
});
|
||||
programB.addExercises([
|
||||
squats,
|
||||
bench,
|
||||
row,
|
||||
triceps,
|
||||
]);
|
||||
|
||||
const programs = [
|
||||
programA,
|
||||
programB,
|
||||
];
|
||||
return programs;
|
||||
}
|
||||
|
||||
// For this spike, just run the first program, what will
|
||||
// really happen is the user picks a program to do from
|
||||
// some menu on a start page.
|
||||
init(buildPrograms()[0]);
|
||||
})();
|
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Loading…
Reference in New Issue