Skip to content

Instantly share code, notes, and snippets.

@jwm-art-net
Last active July 27, 2025 12:49
Show Gist options
  • Select an option

  • Save jwm-art-net/7f555bee4f96d406a95d5f7b6c595e1f to your computer and use it in GitHub Desktop.

Select an option

Save jwm-art-net/7f555bee4f96d406a95d5f7b6c595e1f to your computer and use it in GitHub Desktop.
Simple c program to generate a training plan schedule for learning physical skills, the example is for bicycle trials
/* Trials Skills Schedule 2025-07-26
* Version 2.1 of the schedule
*
* Code :
* https://gist.github.com/jwm-art-net/7f555bee4f96d406a95d5f7b6c595e1f
*
* Text output:
* https://gist.github.com/jwm-art-net/478384456a8782f1b502b6abad31d10c
*
* I created this schedule to help me break out of habits that had
* formed in my trials skill practice. Too often I had a tendency to
* stay in my comfort zone practicing skills I had a degree of
* confidence in, while neglecting the skills I was yet to master.
* The schedule also reduces indecision over what to practice, and is
* a concrete plan I have felt better able to commit to.
*
* SCHEDULE USAGE:
* 1) Start at session #1 and practice each skill in the session!
*
* 2) Set a minimum time to practice each skill in the session.
* I was setting 10 minutes for warm up, and 20 minutes for each skill
* before I added a third skill slot. It's up to you how much time
* you spend, obviously how much depends on your fitness and various
* other factors.
*
* Generally, each skill is performed for the alloted time before
* moving onto the next skill.
*
* 3) The modifier sets a way that one or more of the skills should be
* adapted. Doing this not only adds variety to the way skills are
* practiced, but also helps reduce habits where the same few obstacles
* are used frequently while other obstacles get overlooked.
*
* 4) When and how often you practice a session depends on your
* fitness, and lifestyle. However, at least twice a week, or
* preferably more is recommended. The schedule is not designed around
* a set weekly or monthly practice. It's designed around ensuring a
* range of skills are frequency practiced no matter when you practice.
*
* 5) This schedule I designed for my own personal use without any
* sports training or education beyond what I've self learnt. It has
* no guarantee it will produce progress or be helpful for you!
*
* USAGE NOTES - approach:
*
* The above is the basic approach to using the schedule. However you
* might consider the basic approach to be too strict, limited,
* un-creative, or lacking in freedom.
*
* While still retaining the basic interpretation (practice one skill
* for a set period of time, and then the next, sequentially one after
* another) there are a few things specified in the schedule (to bear
* in mind before going too off-piste) which break things up a little:
*
* 1) The Warm up and Skill C slots have the 'free' skill - it's your
* choice what you do for these.
*
* 2) For the Skill B skill "Comp line" the idea is to set yourself
* a competition trials line - it's compltely up to you which skills
* you use to complete it. Just remember to set difficulty level
* appropriately so you are challenged, but also stand a chance.
*
* 3) The "Line" modifier specifically flips the approach for the
* entire session. When specified, the skills should not be practiced
* one after another for set lengths of time as usual. Instead, similar
* to the Skill B "Comp line" a line of obstacles is set to complete -
* but unlike "Comp line" there's no choice of skills, it's the
* skills specified for the session which must be be used to complete
* the line. The order you use them is up to you however.
*
* USAGE NOTES - time, energy, etc:
*
* I like to set 10 minutes as a minimum time for each part. If I am
* not feeling my best, the session might only last for 40 minutes or
* less, that is perfectly okay.
*
* Often I am more motivated for one part of the session than another.
* I'll spend all my time or energy on the first skill and then lack
* energy or time for the next skill. Nerves might also come into play
* here if I feel at risk during the skill practice. If nerves become
* too agitated then it might be a sign to stop the skill or session
* earlier than expected. Get to know your body.
*
* Injuries or illness might mean missed sessions. When feeling better
* but still not back to normal, then practising only the warm up can
* be a good way of continuing to ride without risking setting yourself
* back again.
*
* Sometimes I go hard on the first skill due to determination and
* might lack time or energy for the rest of the session. No problem.
*
* Other times I simply lack the energy for the full session, even
* with just the minimum of ten minutes for each skill. Nevermind.
*
* Conversely, I might be able to up the times for each skill and have
* a longer session. Judge appropriately!
*
* Combine skills outside of when the "Line" modifier is specified.
* There are no rules saying you can't do this whenever you like, but
* for me personally, while still learning, it's better to more
* frequently focus on just one skill for a set time, before moving
* onto the next skill in the session. This is especially true if the
* skill is physically and mentally demanding.
*
* Tired of it all! Do the minimum! Just get on your bike!
*
* USAGE NOTES - Logging your practice
*
* It can be useful to log your riding in order to see how you've
* progressed. At minimum just note down the skills practiced. If you
* beat any personal bests (ie gap distance, side hop height) be sure
* to write this down also!
*
* BACKGROUND TO CHANGES:
*
* I went through 49 sessions of the first schedule created by my
* small C program. While I have seen improvements in some areas, in
* others (specifically side hops and pedal ups) I had not made
* satisfying progress in the six months the schedule was followed.
*
* Around mid June (currently the end of July) I had thoughts about
* changing the schedule. I made some minor changes, mainly a few
* reclassifications and introductions of obstacles, but I felt too
* committed to the existing schedule and didn't want to ditch it and
* start again just for a few minor modifications.
*
* However now I'm close to 50 sessions (just done #49) I feel better
* positioned to judge how things have gone, and how to improve it
* with some substantial (and some less substantial) changes.
*
* CHANGES:
* The major change is the addition of a third skill slot for each
* session, which has become the new Skill A.
* Due to the dissatisfaction of my side hop and pedal up progress,
* the Skill A slot now consists only of these two skills. Any time I
* did make progress at these skills during a session, it always
* seemed to be lost after a short break. In theory if I alternately
* practice these every single session, progress will be more likely!
*
* I then changed my mind about Skill A every single session, and
* decided to implement a cycle of Skill A practice for 8 sessions
* before having 2 sessions break from it. I just felt it was possibly
* going to be too much to not have a break from it.
*
* Programmatically, each skill slot consist of 3 or 4 groups of
* skills. The slot rotates between each group, and each group rotates
* amongst it's skills each use. Now I've arrived at this second
* version, this method almost seems uneccessary, but it was useful
* for arriving here, as well as having some advantages for increasing
* the frequency of some skills at the cost of over others.
*
* I've cleaned up some of the skills grouping, so for example, in the
* warm up, skinny variations are part of one group, and gap and drop
* variations form groups for Skill B.
*
* Lastly, the "Obstacle" slot has been renamed to the "Modifier" slot.
* I introduced some new "modifiers" but also consolidated some of
* them with existing modifiers. The "Line" modifier (already
* mentioned) is perhaps the most significant as it adapts the way the
* entire session is approached. It came about because I realized it
* was sometimes good fun to combine the skills and practice them
* simultaneously.
*
* CHANGING THE CODE
* If you know how to compile C programs you may be interested in
* changing some of the variables at the start of main(). First up,
* 'sessions' sets the number of sessions generated. Next, two others
* which control output: 'inline_key' (0 for off, 1 for on) will output
* the key before each and every skill (ie "Warm up", "Skill A" etc).
* 'inline_stats' (0 for off, 1 for on) shows a count of how many times
* each skill has been used so far within the schedule, next to the
* skill.
*
* After this, the groups of skills are created. They are simply
* NULL terminated arrays of plain text strings.
*
* Next the groups themselves are grouped into the appropriate arrays
* for warm up, skill a, skill b, skill c, and modifier. During this
* step the plain text arrays are converted into list data structures
* which collect the usage statistics of each skill etc and also help
* simplify the logic of the main loop. The statistic generation
* handles instances of the same string in different groups.
* However statistic generation of the same skill across differing
* slots is undefined. It works, but may cause confusion, which is why
* the "free/w" in Warm up is distinguished from "free/c" in Skill C.
*
* PROGRAM USAGE:
* The program simply outputs text. Redirect the output to a text file.
* The text file can be imported (for example by Libre Office Calc)
* into a spreadsheet by using the pipe symbol '|' as a field
* seperator. Make sure that the comma symbol ',' is not used as a
* field seperator. Adding the colon symbol ':' as a field seperator
* will remove the skill key text into seperate fields to more easily
* make the skills themselves, with the appropriate formatting, more
* prominent than the keys.
*
* After the skills schedule itself, a report of statistics for each
* skill and modifier is output which gives an idea of how often
* certain skills are practiced in relation to each other. This is
* very useful for when you want to taylor the schedule for your
* own particular set of skill strengths and weaknesses.
*/
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
//#define DEBUG 1
typedef struct item {
const char* str;
struct item* next;
int use_count;
int* g_use_count;
char cat;
} item;
typedef struct list {
item* items;
item* tail;
item* ptr;
int maxstrlen;
char cat;
} list;
item* new_item(const char* str)
{
if (!str)
return 0;
item* i = malloc(sizeof(item));
if (!i)
return 0;
i->str = str;
i->next = 0;
i->use_count = 0;
i->g_use_count = &i->use_count;
i->cat = 0;
return i;
}
item* del_item(item* i)
{
if (!i)
return 0;
item* n = i->next;
#ifdef DEBUG
printf("deleting item: '%s'\n", i->str);
#endif
i->str = 0;
i->next = 0;
i->use_count = 0;
i->g_use_count = 0;
i->cat = 0;
free(i);
return n;
}
const char* str_use_item(item* i)
{
if (!i)
return 0;
(*i->g_use_count)++;
if (i->g_use_count != &i->use_count)
i->use_count++;
return i->str;
}
void list_cleanup(list* l)
{
if (!l)
return;
l->ptr = l->items;
#ifdef DEBUG
printf ("deleting list...\n");
#endif
while (l->ptr) {
l->ptr = del_item(l->ptr);
};
free(l);
return;
}
list* new_empty_list(void)
{
#ifdef DEBUG
printf("new empty list\n");
#endif
list* l = malloc(sizeof(list));
if (!l)
return 0;
l->items = 0;
l->ptr = 0;
l->tail = 0;
l->maxstrlen = 0;
return l;
}
list* new_list(char cat, const char** strings)
{
#ifdef DEBUG
printf("new list '%s'...\n", strings[0]);
#endif
list* l = malloc(sizeof(list));
if (!l)
return 0;
l->items = 0;
l->ptr = 0;
l->tail = 0;
l->maxstrlen = 0;
l->cat = cat;
const char* str = strings[0];
if (!(l->items = new_item(str))) {
free(l);
return 0;
}
l->maxstrlen = strlen(str);
l->tail = l->items;
l->items->cat = cat;
int strix = 0;
int slen = 0;
while((str = strings[++strix])) {
#ifdef DEBUG
printf("adding new item '%s' to list...\n", str);
#endif
item* i = new_item(str);
if (!i) {
#ifdef DEBUG
printf("FAIL: item not added\n");
#endif
list_cleanup(l);
return 0;
}
slen = strlen(str);
if (slen > l->maxstrlen)
l->maxstrlen = slen;
l->tail->next = i;
l->tail = i;
i->cat = cat;
}
return l;
}
item* list_head(list* l)
{
if (!l)
return 0;
l->ptr = l->items;
return l->ptr;
}
item* list_at_head(list* l)
{
if (!l)
return 0;
return (l->ptr == l->items) ? l->ptr : 0;
}
item* list_at_tail(list* l)
{
if (!l)
return 0;
return (l->ptr == l->tail) ? l->ptr : 0;
}
item* list_next(list* l)
{
if (!l)
return 0;
l->ptr = l->ptr->next;
return l->ptr;
}
item* list_loop(list* l)
{
if (!l)
return 0;
if (!l->ptr)
l->ptr = l->items;
else
l->ptr = l->ptr->next;
if (!l->ptr)
l->ptr = l->items;
return l->ptr;
}
item* list_add(list* l, const char* str, char cat)
{
if (!l)
return 0;
if (!l->items) {
if (!(l->items = new_item(str)))
return 0;
l->tail = l->ptr = l->items;
}
else {
if (!(l->tail->next = new_item(str)))
return 0;
l->ptr = l->tail->next;
l->tail = l->ptr;
}
l->tail->cat = cat;
return l->tail;
}
item* list_sort_by_global_use_count(list* l)
{
if (!l)
return 0;
/* steps through list and compares each item with next item */
int changed = 0;
do {
item* i = l->items;
item* p = 0;
item* n = i->next;
int changed = 0;
while(n) {
if (*(i->g_use_count) < *(n->g_use_count)) {
if (p == 0) {
i->next = n->next;
n->next = i;
l->items = n;
}
else {
p->next = n;
i->next = n->next;
n->next = i;
}
changed = 1;
break;
}
else {
p = i;
i = n;
n = i->next;
}
}
if (!n)
break;
} while (1);
return 0;
}
list* lg = 0;
list* global_add_list_no_dup(list* l)
{
if (!lg)
lg = new_empty_list();
if (!lg) {
#ifdef DEBUG
printf("lg = 0\n");
#endif
return 0;
}
if (!l) {
#ifdef DEBUG
printf("l = 0\n");
#endif
return 0;
}
list_head(l);
while (l->ptr) {
list_head(lg);
while (lg->ptr) {
#ifdef DEBUG
//printf("comparing: '%s' with '%s'\n", lg->ptr->str, l->ptr->str);
#endif
if (strcmp(lg->ptr->str, l->ptr->str) == 0)
break;
list_next(lg);
}
if (!lg->ptr) {
#ifdef DEBUG
//printf("adding non duplicate item: '%s'\n", l->ptr->str);
#endif
list_add(lg, l->ptr->str, l->ptr->cat);
}
l->ptr->g_use_count = &lg->ptr->use_count;
list_next(l);
}
}
int main(int argc, char** argv)
{
int sessions = 50; // number of sessions to generate
// 0 = off, 1 = on
int inline_stats = 0; // show use count within the schedule next to each skill in every session
int inline_key = 1; // show key/slot name next to before each skill ie "Warm up:" "Skill A" etc
int inline_key_pipe = 1; // add a | symbol (used as field seperator) when using the inline_key
int skill_a_sessions = 8; // initiate a break from Skill A after this many sessions.
int skill_a_rest = 2; // how many sessions to rest from Skill A for.
const char* str_w1[] = { "Long skinny", "Skinny corners", "Skinny steps", "High skinny", 0 };
const char* str_w2[] = { "Small hops, gaps, drops", "Rear to edge", "Endo, trackstand, fakie", 0};
const char* str_w3[] = { "Rear wheel pivots", "Pivots up", "Wrong foot rear hops", 0};
const char* str_w4[] = { "Free/w", "Pedal to manual", "Endo, trackstand, fakie", 0};
//---
const char* str_a1[] = { "Pedal ups", "Side hops", 0 };
//---
const char* str_b1[] = { "Lunge", "Bunny hops", "High roll ups", 0 };
const char* str_b2[] = { "Long gaps", "High gaps", "Drop gaps", 0 };
const char* str_b3[] = { "High drops", "Precision drops", "Comp line", 0 };
//---
const char* str_c1[] = { "Front hops", "Wheel swap", 0 };
const char* str_c2[] = { "Wheelbase hops", "Rolling hops", 0 };
const char* str_c3[] = { "Rotational hops", "Rear wheel twist", 0};
const char* str_c7[] = { "Free/c", "Wheel swap", 0 };
//---
const char* mods[] = { "L-shape",
"Rounded/Natural",
"Off-camber",
"Wall",
"A-beam/Rail/Post",
"Incline",
"Small/Narrow",
"High",
"Bunny hop bar",
"4PH Trapezoid",
"Line", 0 };
//list* warmup = new_list(str_w);
list* warmup[] = { new_list('w', str_w1), new_list('w', str_w2), new_list('w', str_w3), new_list('w', str_w4), 0 };
list* skills_a[] = { new_list('a', str_a1), 0 };
list* skills_b[] = { new_list('b', str_b1), new_list('b', str_b2), new_list('b', str_b3), 0 };
list* skills__c[] = { new_list('c', str_c1), new_list('c',str_c2), new_list('c',str_c3), new_list('c',str_c7), 0 };
list* skills_c[] = { skills__c[0], skills__c[1], skills__c[2], skills__c[0], skills__c[1], skills__c[2], skills__c[3], 0 };
list* modifiers = new_list('m', mods);
time_t ct = time(NULL);
char* const ts = ctime(&ct);
printf("|||||see: https://gist.github.com/jwm-art-net/7f555bee4f96d406a95d5f7b6c595e1f\n");
printf("|||||generated by %s on %s\n", __FILE_NAME__, ts);
int sesh = 1;
int skw = 0, ska = 0, skb = 0, skc = 0, skm = 0;
//global_add_list_no_dup(warmup);
while (warmup[skw]) global_add_list_no_dup(warmup[skw++]);
while (skills_a[ska]) global_add_list_no_dup(skills_a[ska++]);
while (skills_b[skb]) global_add_list_no_dup(skills_b[skb++]);
while (skills_c[skc]) global_add_list_no_dup(skills_c[skc++]);
global_add_list_no_dup(modifiers);
int wmaxsl = 0;
int skamaxsl = 0;
int skbmaxsl = 0;
int skcmaxsl = 0;
skw = ska = skb = skc = skm = 0;
while (warmup[skw]) {
if (warmup[skw]->maxstrlen > wmaxsl)
wmaxsl = warmup[skw]->maxstrlen;
skw++;
}
while (skills_a[ska]) {
if (skills_a[ska]->maxstrlen > skamaxsl)
skamaxsl = skills_a[ska]->maxstrlen;
ska++;
}
while (skills_b[skb]) {
if (skills_b[skb]->maxstrlen > skbmaxsl)
skbmaxsl = skills_b[skb]->maxstrlen;
skb++;
}
while (skills_c[skc]) {
if (skills_c[skc]->maxstrlen > skcmaxsl)
skcmaxsl = skills_c[skc]->maxstrlen;
skc++;
}
char hdr[1000];
char fmt[1000];
const char* keys[] = { "Warm up", "Skill A", "Skill B", "Skill C", "Modifier" };
char ikey[5][80]; // array of character strings for inline keys
if (inline_key) {
int ik = 0;
for (int ik = 0; ik < 5; ik++)
sprintf(ikey[ik], "%s: %s", keys[ik], inline_key_pipe ? "| " : "");
}
else {
// not using inline_keys so initialize to zero length character strings
for (int ik = 0; ik < 5; ik++)
ikey[ik][0] = '\0';
}
if (inline_stats) {
if (!inline_key)
sprintf(hdr, "#%%-2s | %%-%ds %%-4s | %%-%ds %%-4s | %%-%ds %%-4s | %%-%ds %%-4s | %%-%ds %%-4s\n",
wmaxsl , skamaxsl, skbmaxsl, skcmaxsl, modifiers->maxstrlen);
sprintf(fmt, "#%%-2d | %s%%-%ds %%-4s | %s%%-%ds %%-4s | %s%%-%ds %%-4s | %s%%-%ds %%-4s | %s%%-%ds %%-4s\n",
ikey[0], wmaxsl , ikey[1], skamaxsl , ikey[2], skbmaxsl , ikey[3], skcmaxsl, ikey[4], modifiers->maxstrlen);
}
else {
if (!inline_key)
sprintf(hdr, "#%%-2s | %%-%ds %%-4s | %%-%ds %%-4s | %%-%ds %%-4s | %%-%ds %%-4s | %%-%ds %%-4s\n",
wmaxsl , skamaxsl, skbmaxsl, skcmaxsl, modifiers->maxstrlen);
sprintf(fmt, "#%%-2d | %s%%-%ds | %s%%-%ds | %s%%-%ds | %s%%-%ds | %s%%-%ds\n",
ikey[0], wmaxsl , ikey[1], skamaxsl , ikey[2], skbmaxsl , ikey[3], skcmaxsl, ikey[4], modifiers->maxstrlen);
}
if (!inline_key) {
const char* ul1 = "---------";
const char* ul2 = "----";
printf(hdr, " ", keys[0], "", keys[1], "", keys[2], "", keys[3], "", keys[4], "");
printf(hdr, "--", ul1, ul2, ul1, ul2, ul1, ul2, ul1, ul2, ul1, ul2);
}
skw = ska = skb = skc = 0;
int ska_count = 0;
int ska_r_count = 0;
while (sessions-- > 0) {
item* i1 = list_loop(warmup[skw++]);
item* i2 = 0;
item* i3 = list_loop(skills_b[skb++]);
item* i4 = list_loop(skills_c[skc++]);
item* i5 = list_loop(modifiers);
const char* s1 = str_use_item(i1);
const char* s2;
const char* s3 = str_use_item(i3);
const char* s4 = str_use_item(i4);
const char* s5 = str_use_item(i5);
if (ska_count < skill_a_sessions) {
i2 = list_loop(skills_a[ska++]);
s2 = str_use_item(i2);
ska_count++;
} else {
s2 = " - ";
ska_r_count++;
if (ska_r_count == skill_a_rest) {
ska_count = 0;
ska_r_count = 0;
}
}
if (inline_stats) {
char t1[10]; sprintf(t1, "| %d", *(i1->g_use_count));
char t2[10] = "";
if (i2) {
sprintf(t2, "| %d", *(i2->g_use_count));
}
char t3[10]; sprintf(t3, "| %d", *(i3->g_use_count));
char t4[10]; sprintf(t4, "| %d", *(i4->g_use_count));
char t5[10]; sprintf(t5, "| %d", *(i5->g_use_count));
printf(fmt, sesh++, s1, t1, s2, t2, s3, t3, s4, t4, s5, t5);
}
else
printf(fmt, sesh++, s1, s2, s3, s4, s5);
if (warmup[skw] == 0)
skw = 0;
if (skills_a[ska] == 0)
ska = 0;
if (skills_b[skb] == 0)
skb = 0;
if (skills_c[skc] == 0)
skc = 0;
}
skw = 0; while (warmup[skw]) list_cleanup(warmup[skw++]);
ska = 0; while (skills_a[ska]) list_cleanup(skills_a[ska++]);
skb = 0; while (skills_b[skb]) list_cleanup(skills_b[skb++]);
skc = 0; while (skills__c[skc]) list_cleanup(skills__c[skc++]);
list_cleanup(modifiers);
list_sort_by_global_use_count(lg);
char cats[] = {'w', 'a', 'b', 'c', 'm', 0};
for (int i = 0; cats[i] != 0; i++) {
printf(" |-------------------\n");
item* li = list_head(lg);
while (li) {
if (li->cat == cats[i])
printf(" | skill: (%c) %-24s | use count:%-2d\n", li->cat, li->str, li->use_count);
li = li->next;
}
}
list_cleanup(lg);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment