1
0
Fork 0

Initial commit of BuffGym 5x5 training program

master
Paul Cockrell 2020-04-20 15:00:06 +01:00
parent e052a6e65f
commit 74bd784b09
8 changed files with 660 additions and 0 deletions

View File

@ -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}
]
}
]

View File

@ -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"
]
}
}

View File

@ -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");
}
}

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AFEEolAC6lN7vdDCcECwPd6guVGCYuDC4cCBQMikQXQJAMjkECmcyIx4XDmUjmYvLC4XUDARHBIoIWLgATCGQdA7tEonQC5ouDDYg0BOxgSEAggwKRwgUCC6ZIDSwoXNogWDDgNCAgIWIkUEoUk6kiCgMkokipsiBIQXIki2CAgNCAoYADC5Eic4Mic4ICCAIIJCC5MzAAcykYGEAAIXOABAXTmUzGoIXVAIIXLB4SICDIovjO76PZbYR3PDI4XiI6530MIh3SC6R33C/oAOC48CCxsgC44A/ADY="))

View File

@ -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);
}
}

View File

@ -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--;
}
}

311
apps/buffgym/buffgym.app.js Executable file
View File

@ -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]);
})();

BIN
apps/buffgym/buffgym.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB