Refine controls and add dual run-all reports

This commit is contained in:
2026-08-09 18:33:16 +02:00
parent a8ba3760f4
commit 2e63f9cbbd
2 changed files with 15 additions and 11 deletions
+4
View File
@@ -12,6 +12,10 @@ reports its duration with microsecond-derived precision. The bar animation is
rendered through a PSRAM-backed canvas for tear-free updates. Build with rendered through a PSRAM-backed canvas for tear-free updates. Build with
`pio run`, upload with `pio run -t upload`. `pio run`, upload with `pio run -t upload`.
**All Visual** runs the eight faster animations and then opens a comparison
report. **All Fast** benchmarks all eight without intermediate drawing and
opens the same report with operation-count and measured-time bars.
The PlatformIO platform is pinned to the `54.03.21-2` maintenance release, The PlatformIO platform is pinned to the `54.03.21-2` maintenance release,
which includes esptool 5.0.2 and avoids the Click API error present with the which includes esptool 5.0.2 and avoids the Click API error present with the
original `54.03.21` package on some Windows installations. original `54.03.21` package on some Windows installations.
+11 -11
View File
@@ -20,35 +20,35 @@ int a[N],base[N],tmp[N],which=0,hotA=-1,hotB=-1;
uint32_t cmp=0,moves=0,startMs=0,startUs=0,pauseStart=0,pauseTotal=0,lastFrame=0; uint32_t cmp=0,moves=0,startMs=0,startUs=0,pauseStart=0,pauseTotal=0,lastFrame=0;
bool paused=false,abortRun=false,redo=false,runAll=false,benchmark=false; bool paused=false,abortRun=false,redo=false,runAll=false,benchmark=false;
M5Canvas barCanvas(&M5.Display); M5Canvas barCanvas(&M5.Display);
const Rect pauseB{24,620,160,70},redoB{196,620,140,70},nextB{348,620,140,70},allB{500,620,180,70},benchB{692,620,250,70},menuB{954,620,302,70}; const Rect pauseB{18,620,150,70},redoB{178,620,120,70},nextB{308,620,120,70},benchB{438,620,180,70},allB{628,620,170,70},fastAllB{808,620,170,70},menuB{988,620,274,70};
void label(const char*s,int x,int y,textdatum_t d,const lgfx::IFont*f,uint16_t c=TFT_WHITE,uint16_t bg=BG){M5.Display.setFont(f);M5.Display.setTextDatum(d);M5.Display.setTextColor(c,bg);M5.Display.drawString(s,x,y);} void label(const char*s,int x,int y,textdatum_t d,const lgfx::IFont*f,uint16_t c=TFT_WHITE,uint16_t bg=BG){M5.Display.setFont(f);M5.Display.setTextDatum(d);M5.Display.setTextColor(c,bg);M5.Display.drawString(s,x,y);}
void button(Rect r,const char*s,uint16_t c){M5.Display.fillRoundRect(r.x,r.y,r.w,r.h,14,c);M5.Display.drawRoundRect(r.x,r.y,r.w,r.h,14,TFT_WHITE);label(s,r.x+r.w/2,r.y+r.h/2,middle_center,&fonts::FreeSansBold12pt7b);} void button(Rect r,const char*s,uint16_t c){M5.Display.fillRoundRect(r.x,r.y,r.w,r.h,14,c);M5.Display.drawRoundRect(r.x,r.y,r.w,r.h,14,TFT_WHITE);M5.Display.setFont(&fonts::FreeSansBold9pt7b);M5.Display.setTextDatum(middle_center);M5.Display.setTextColor(c==YELLOW?BG:TFT_WHITE);M5.Display.drawString(s,r.x+r.w/2,r.y+r.h/2);}
void buttons(){button(pauseB,paused?"RESUME":"PAUSE",paused?GREEN:0x39A7);button(redoB,"REDO",0x620B);button(nextB,"NEXT",0x2B8E);button(allB,"RUN ALL",0x4A2E);button(benchB,"NO VISUAL",YELLOW);button(menuB,"ALGORITHM MENU",0x31A6);} void buttons(){button(pauseB,paused?"RESUME":"PAUSE",paused?GREEN:0x39A7);button(redoB,"REDO",0x620B);button(nextB,"NEXT",0x2B8E);button(benchB,"NO VISUAL",YELLOW);button(allB,"ALL VISUAL",0x4A2E);button(fastAllB,"ALL FAST",0x6A2E);button(menuB,"ALGORITHM MENU",0x31A6);}
void header(){M5.Display.fillRect(0,0,1280,112,PANEL);M5.Display.fillRect(0,108,1280,4,CYAN);label(alg[which].name,32,40,middle_left,&fonts::FreeSansBold24pt7b);label(alg[which].cost,1246,36,middle_right,&fonts::FreeSansBold18pt7b,YELLOW,PANEL);label(alg[which].lesson,34,88,middle_left,&fonts::FreeSans12pt7b,MUTED,PANEL);} void header(){M5.Display.fillRect(0,0,1280,112,PANEL);M5.Display.fillRect(0,108,1280,4,CYAN);label(alg[which].name,32,40,middle_left,&fonts::FreeSansBold24pt7b);label(alg[which].cost,1246,36,middle_right,&fonts::FreeSansBold18pt7b,YELLOW,PANEL);label(alg[which].lesson,34,88,middle_left,&fonts::FreeSans12pt7b,MUTED,PANEL);}
void chart(bool force=false){uint32_t now=millis();if(!force&&now-lastFrame<16)return;lastFrame=now;barCanvas.fillSprite(BG);int gap=3,bw=(CW-(N-1)*gap)/N;for(int i=0;i<N;i++){int h=map(a[i],1,N,12,CH-18);uint16_t c=(i==hotA||i==hotB)?YELLOW:CYAN;barCanvas.fillRoundRect(i*(bw+gap),CH-h,bw,h,min(4,bw/2),c);}barCanvas.pushSprite(CX,CY);char s[110];snprintf(s,sizeof(s),"COMPARISONS %lu MOVES %lu %s",(unsigned long)cmp,(unsigned long)moves,benchmark?"BENCHMARK: DISPLAY OFF":(paused?"PAUSED":"SORTING..."));M5.Display.fillRect(34,562,1212,46,BG);label(s,34,585,middle_left,&fonts::FreeSans12pt7b,MUTED);} void chart(bool force=false){uint32_t now=millis();if(!force&&now-lastFrame<16)return;lastFrame=now;barCanvas.fillSprite(BG);int gap=3,bw=(CW-(N-1)*gap)/N;for(int i=0;i<N;i++){int h=map(a[i],1,N,12,CH-18);uint16_t c=(i==hotA||i==hotB)?YELLOW:CYAN;barCanvas.fillRoundRect(i*(bw+gap),CH-h,bw,h,min(4,bw/2),c);}barCanvas.pushSprite(CX,CY);char s[110];snprintf(s,sizeof(s),"COMPARISONS %lu MOVES %lu %s",(unsigned long)cmp,(unsigned long)moves,benchmark?"BENCHMARK: DISPLAY OFF":(paused?"PAUSED":"SORTING..."));M5.Display.fillRect(34,562,1212,46,BG);label(s,34,585,middle_left,&fonts::FreeSans12pt7b,MUTED);}
void touch(){M5.update();auto t=M5.Touch.getDetail();if(!t.wasReleased())return;if(pauseB.has(t.x,t.y)){paused=!paused;if(paused)pauseStart=millis();else pauseTotal+=millis()-pauseStart;buttons();}else if(redoB.has(t.x,t.y)){redo=true;abortRun=true;paused=false;}else if(nextB.has(t.x,t.y)){which=(which+1)%8;abortRun=true;paused=false;runAll=false;benchmark=false;}else if(allB.has(t.x,t.y)){which=0;redo=true;abortRun=true;paused=false;runAll=true;benchmark=false;for(auto&r:results)r=Result{};}else if(benchB.has(t.x,t.y)){benchmark=true;redo=true;abortRun=true;paused=false;runAll=false;}else if(menuB.has(t.x,t.y)){abortRun=true;paused=false;runAll=false;benchmark=false;}} void touch(){M5.update();auto t=M5.Touch.getDetail();if(!t.wasReleased())return;if(pauseB.has(t.x,t.y)){paused=!paused;if(paused)pauseStart=millis();else pauseTotal+=millis()-pauseStart;buttons();}else if(redoB.has(t.x,t.y)){redo=true;abortRun=true;paused=false;}else if(nextB.has(t.x,t.y)){which=(which+1)%8;abortRun=true;paused=false;runAll=false;benchmark=false;}else if(allB.has(t.x,t.y)){which=0;redo=true;abortRun=true;paused=false;runAll=true;benchmark=false;for(auto&r:results)r=Result{};}else if(fastAllB.has(t.x,t.y)){which=0;redo=true;abortRun=true;paused=false;runAll=true;benchmark=true;for(auto&r:results)r=Result{};}else if(benchB.has(t.x,t.y)){benchmark=true;redo=true;abortRun=true;paused=false;runAll=false;}else if(menuB.has(t.x,t.y)){abortRun=true;paused=false;runAll=false;benchmark=false;}}
bool frame(int x=-1,int y=-1,int ms=7){if(benchmark)return true;hotA=x;hotB=y;chart();uint32_t end=millis()+ms;do{touch();while(paused&&!abortRun){touch();chart(true);delay(12);}if(abortRun)return false;delay(1);}while((int32_t)(end-millis())>0);return true;} bool frame(int x=-1,int y=-1,int ms=2){if(benchmark)return true;hotA=x;hotB=y;chart();uint32_t end=millis()+ms;do{touch();while(paused&&!abortRun){touch();chart(true);delay(12);}if(abortRun)return false;delay(1);}while((int32_t)(end-millis())>0);return true;}
void swapv(int x,int y){if(x==y)return;int v=a[x];a[x]=a[y];a[y]=v;moves+=3;}void writev(int i,int v){a[i]=v;moves++;} void swapv(int x,int y){if(x==y)return;int v=a[x];a[x]=a[y];a[y]=v;moves+=3;}void writev(int i,int v){a[i]=v;moves++;}
bool bubble(){for(int end=N-1;end>0;end--){bool changed=false;for(int i=0;i<end;i++){cmp++;if(a[i]>a[i+1]){swapv(i,i+1);changed=true;}if(!frame(i,i+1))return false;}if(!changed)break;}return true;} bool bubble(){for(int end=N-1;end>0;end--){bool changed=false;for(int i=0;i<end;i++){cmp++;if(a[i]>a[i+1]){swapv(i,i+1);changed=true;}if(!frame(i,i+1))return false;}if(!changed)break;}return true;}
bool insertion(){for(int i=1;i<N;i++){int key=a[i],j=i-1;moves++;while(j>=0){cmp++;if(a[j]<=key)break;writev(j+1,a[j]);if(!frame(j,j+1))return false;j--;}writev(j+1,key);if(!frame(j+1,i))return false;}return true;} bool insertion(){for(int i=1;i<N;i++){int key=a[i],j=i-1;moves++;while(j>=0){cmp++;if(a[j]<=key)break;writev(j+1,a[j]);if(!frame(j,j+1))return false;j--;}writev(j+1,key);if(!frame(j+1,i))return false;}return true;}
bool selection(){for(int i=0;i<N-1;i++){int best=i;for(int j=i+1;j<N;j++){cmp++;if(a[j]<a[best])best=j;if(!frame(best,j,4))return false;}swapv(i,best);if(!frame(i,best))return false;}return true;} bool selection(){for(int i=0;i<N-1;i++){int best=i;for(int j=i+1;j<N;j++){cmp++;if(a[j]<a[best])best=j;if(!frame(best,j,1))return false;}swapv(i,best);if(!frame(i,best))return false;}return true;}
bool shell(){for(int gap=N/2;gap>0;gap/=2)for(int i=gap;i<N;i++){int v=a[i],j=i;moves++;while(j>=gap){cmp++;if(a[j-gap]<=v)break;writev(j,a[j-gap]);if(!frame(j-gap,j))return false;j-=gap;}writev(j,v);if(!frame(j,i))return false;}return true;} bool shell(){for(int gap=N/2;gap>0;gap/=2)for(int i=gap;i<N;i++){int v=a[i],j=i;moves++;while(j>=gap){cmp++;if(a[j-gap]<=v)break;writev(j,a[j-gap]);if(!frame(j-gap,j))return false;j-=gap;}writev(j,v);if(!frame(j,i))return false;}return true;}
bool mergePart(int lo,int mid,int hi){int i=lo,j=mid+1,k=lo;while(i<=mid&&j<=hi){cmp++;tmp[k++]=(a[i]<=a[j])?a[i++]:a[j++];moves++;if(!frame(min(i,mid),min(j,hi),5))return false;}while(i<=mid){tmp[k++]=a[i++];moves++;}while(j<=hi){tmp[k++]=a[j++];moves++;}for(i=lo;i<=hi;i++){writev(i,tmp[i]);if(!frame(i,-1,5))return false;}return true;} bool mergePart(int lo,int mid,int hi){int i=lo,j=mid+1,k=lo;while(i<=mid&&j<=hi){cmp++;tmp[k++]=(a[i]<=a[j])?a[i++]:a[j++];moves++;if(!frame(min(i,mid),min(j,hi),2))return false;}while(i<=mid){tmp[k++]=a[i++];moves++;}while(j<=hi){tmp[k++]=a[j++];moves++;}for(i=lo;i<=hi;i++){writev(i,tmp[i]);if(!frame(i,-1,2))return false;}return true;}
bool mergeSort(int lo,int hi){if(lo>=hi)return true;int mid=(lo+hi)/2;return mergeSort(lo,mid)&&mergeSort(mid+1,hi)&&mergePart(lo,mid,hi);} bool mergeSort(int lo,int hi){if(lo>=hi)return true;int mid=(lo+hi)/2;return mergeSort(lo,mid)&&mergeSort(mid+1,hi)&&mergePart(lo,mid,hi);}
bool quick(int lo,int hi){if(lo>=hi)return true;int mid=(lo+hi)/2;cmp+=3;if(a[mid]<a[lo])swapv(mid,lo);if(a[hi]<a[lo])swapv(hi,lo);if(a[mid]<a[hi])swapv(mid,hi);int pivot=a[hi],i=lo-1;moves++;for(int j=lo;j<hi;j++){cmp++;if(a[j]<=pivot){i++;swapv(i,j);}if(!frame(j,hi))return false;}swapv(i+1,hi);int p=i+1;if(!frame(p,hi))return false;return quick(lo,p-1)&&quick(p+1,hi);} bool quick(int lo,int hi){if(lo>=hi)return true;int mid=(lo+hi)/2;cmp+=3;if(a[mid]<a[lo])swapv(mid,lo);if(a[hi]<a[lo])swapv(hi,lo);if(a[mid]<a[hi])swapv(mid,hi);int pivot=a[hi],i=lo-1;moves++;for(int j=lo;j<hi;j++){cmp++;if(a[j]<=pivot){i++;swapv(i,j);}if(!frame(j,hi))return false;}swapv(i+1,hi);int p=i+1;if(!frame(p,hi))return false;return quick(lo,p-1)&&quick(p+1,hi);}
bool heapify(int size,int root){while(true){int big=root,l=root*2+1,r=l+1;if(l<size){cmp++;if(a[l]>a[big])big=l;}if(r<size){cmp++;if(a[r]>a[big])big=r;}if(big==root)return true;swapv(root,big);if(!frame(root,big))return false;root=big;}} bool heapify(int size,int root){while(true){int big=root,l=root*2+1,r=l+1;if(l<size){cmp++;if(a[l]>a[big])big=l;}if(r<size){cmp++;if(a[r]>a[big])big=r;}if(big==root)return true;swapv(root,big);if(!frame(root,big))return false;root=big;}}
bool heap(){for(int i=N/2-1;i>=0;i--)if(!heapify(N,i))return false;for(int e=N-1;e>0;e--){swapv(0,e);if(!frame(0,e))return false;if(!heapify(e,0))return false;}return true;} bool heap(){for(int i=N/2-1;i>=0;i--)if(!heapify(N,i))return false;for(int e=N-1;e>0;e--){swapv(0,e);if(!frame(0,e))return false;if(!heapify(e,0))return false;}return true;}
bool radix(){for(int exp=1;N/exp>0;exp*=10){int count[10]={0};for(int i=0;i<N;i++)count[(a[i]/exp)%10]++;for(int i=1;i<10;i++)count[i]+=count[i-1];for(int i=N-1;i>=0;i--){tmp[--count[(a[i]/exp)%10]]=a[i];moves++;}for(int i=0;i<N;i++){writev(i,tmp[i]);if(!frame(i,-1,9))return false;}}return true;} bool radix(){for(int exp=1;N/exp>0;exp*=10){int count[10]={0};for(int i=0;i<N;i++)count[(a[i]/exp)%10]++;for(int i=1;i<10;i++)count[i]+=count[i-1];for(int i=N-1;i>=0;i--){tmp[--count[(a[i]/exp)%10]]=a[i];moves++;}for(int i=0;i<N;i++){writev(i,tmp[i]);if(!frame(i,-1,3))return false;}}return true;}
bool sortNow(){switch(which){case 0:return bubble();case 1:return insertion();case 2:return selection();case 3:return shell();case 4:return mergeSort(0,N-1);case 5:return quick(0,N-1);case 6:return heap();default:return radix();}} bool sortNow(){switch(which){case 0:return bubble();case 1:return insertion();case 2:return selection();case 3:return shell();case 4:return mergeSort(0,N-1);case 5:return quick(0,N-1);case 6:return heap();default:return radix();}}
void reset(){for(int i=0;i<N;i++)a[i]=base[i];cmp=moves=pauseTotal=0;paused=abortRun=redo=false;} void reset(){for(int i=0;i<N;i++)a[i]=base[i];cmp=moves=pauseTotal=0;paused=abortRun=redo=false;}
void runScreen(){M5.Display.fillScreen(BG);header();buttons();chart(true);} void runScreen(){M5.Display.fillScreen(BG);header();buttons();chart(true);}
bool waitFor(uint32_t ms){uint32_t s=millis();while(millis()-s<ms){touch();if(abortRun)return false;delay(10);}return true;} bool waitFor(uint32_t ms){uint32_t s=millis();while(millis()-s<ms){touch();if(abortRun)return false;delay(10);}return true;}
void result(){hotA=hotB=-1;chart(true);delay(150);Rect c{246,205,788,300};M5.Display.fillRoundRect(c.x,c.y,c.w,c.h,24,PANEL);M5.Display.drawRoundRect(c.x,c.y,c.w,c.h,24,CYAN);label(benchmark?"BENCHMARK DONE!":"SORTED!",640,250,middle_center,&fonts::FreeSansBold24pt7b,GREEN,PANEL);const char* n[]={"COMPARISONS","MOVES","TIME"};for(int i=0;i<3;i++){label(n[i],430,330+i*58,middle_left,&fonts::FreeSansBold12pt7b,MUTED,PANEL);label("...",850,330+i*58,middle_right,&fonts::FreeSansBold18pt7b,YELLOW,PANEL);delay(benchmark?0:160);}char v[32];snprintf(v,sizeof(v),"%lu",(unsigned long)cmp);M5.Display.fillRect(650,305,240,42,PANEL);label(v,850,330,middle_right,&fonts::FreeSansBold18pt7b,YELLOW,PANEL);if(!benchmark)delay(200);snprintf(v,sizeof(v),"%lu",(unsigned long)moves);M5.Display.fillRect(650,363,240,42,PANEL);label(v,850,388,middle_right,&fonts::FreeSansBold18pt7b,YELLOW,PANEL);if(!benchmark)delay(200);snprintf(v,sizeof(v),"%.3f ms",results[which].us/1000.0f);M5.Display.fillRect(650,421,240,42,PANEL);label(v,850,446,middle_right,&fonts::FreeSansBold18pt7b,YELLOW,PANEL);} void result(){hotA=hotB=-1;chart(true);delay(150);Rect c{246,205,788,300};M5.Display.fillRoundRect(c.x,c.y,c.w,c.h,24,PANEL);M5.Display.drawRoundRect(c.x,c.y,c.w,c.h,24,CYAN);label(benchmark?"BENCHMARK DONE!":"SORTED!",640,250,middle_center,&fonts::FreeSansBold24pt7b,GREEN,PANEL);const char* n[]={"COMPARISONS","MOVES","TIME"};for(int i=0;i<3;i++){label(n[i],430,330+i*58,middle_left,&fonts::FreeSansBold12pt7b,MUTED,PANEL);label("...",850,330+i*58,middle_right,&fonts::FreeSansBold18pt7b,YELLOW,PANEL);delay(benchmark?0:160);}char v[32];snprintf(v,sizeof(v),"%lu",(unsigned long)cmp);M5.Display.fillRect(650,305,240,42,PANEL);label(v,850,330,middle_right,&fonts::FreeSansBold18pt7b,YELLOW,PANEL);if(!benchmark)delay(200);snprintf(v,sizeof(v),"%lu",(unsigned long)moves);M5.Display.fillRect(650,363,240,42,PANEL);label(v,850,388,middle_right,&fonts::FreeSansBold18pt7b,YELLOW,PANEL);if(!benchmark)delay(200);snprintf(v,sizeof(v),"%.3f ms",results[which].us/1000.0f);M5.Display.fillRect(650,421,240,42,PANEL);label(v,850,446,middle_right,&fonts::FreeSansBold18pt7b,YELLOW,PANEL);}
void summary(){M5.Display.fillScreen(BG);M5.Display.fillRect(0,0,1280,96,PANEL);label("SAME DATA: ALL 8 ALGORITHMS",32,40,middle_left,&fonts::FreeSansBold24pt7b,TFT_WHITE,PANEL);label("OPERATIONS",1246,40,middle_right,&fonts::FreeSansBold12pt7b,YELLOW,PANEL);uint32_t top=1;for(auto&r:results)top=max(top,r.cmp+r.moves);for(int i=0;i<8;i++){int y=125+i*62;uint32_t ops=results[i].cmp+results[i].moves;int w=(uint64_t)ops*700/top;label(alg[i].name,38,y+20,middle_left,&fonts::FreeSans12pt7b);M5.Display.fillRoundRect(330,y,700,38,8,PANEL2);M5.Display.fillRoundRect(330,y,max(3,w),38,8,i==7?GREEN:CYAN);char s[48];snprintf(s,sizeof(s),"%lu %.1fms",(unsigned long)ops,results[i].us/1000.0f);label(s,1238,y+20,middle_right,&fonts::FreeSans12pt7b,MUTED);}label("Operations = comparisons + moves. Tap anywhere for the menu.",640,682,middle_center,&fonts::FreeSans12pt7b,MUTED);while(true){M5.update();if(M5.Touch.getDetail().wasReleased())break;delay(12);}} void summary(){M5.Display.fillScreen(BG);M5.Display.fillRect(0,0,1280,104,PANEL);label(benchmark?"PERFORMANCE REPORT - NO VISUAL":"ANIMATED RUN REPORT",32,36,middle_left,&fonts::FreeSansBold24pt7b,TFT_WHITE,PANEL);label("OPERATIONS",900,34,middle_right,&fonts::FreeSans9pt7b,CYAN,PANEL);label("TIME",1238,34,middle_right,&fonts::FreeSans9pt7b,YELLOW,PANEL);label("Same 48 shuffled values for every algorithm",34,82,middle_left,&fonts::FreeSans9pt7b,MUTED,PANEL);uint32_t topOps=1,topUs=1;for(auto&r:results){topOps=max(topOps,r.cmp+r.moves);topUs=max(topUs,r.us);}for(int i=0;i<8;i++){int y=122+i*65;uint32_t ops=results[i].cmp+results[i].moves;int ow=(uint64_t)ops*560/topOps,tw=(uint64_t)results[i].us*240/topUs;label(alg[i].name,32,y+25,middle_left,&fonts::FreeSans9pt7b);M5.Display.fillRoundRect(260,y,560,18,6,PANEL2);M5.Display.fillRoundRect(260,y,max(3,ow),18,6,CYAN);M5.Display.fillRoundRect(260,y+27,240,15,6,PANEL2);M5.Display.fillRoundRect(260,y+27,max(3,tw),15,6,YELLOW);char os[24],ts[24];snprintf(os,sizeof(os),"%lu ops",(unsigned long)ops);snprintf(ts,sizeof(ts),"%.3f ms",results[i].us/1000.0f);label(os,850,y+9,middle_left,&fonts::FreeSans9pt7b,CYAN);label(ts,1238,y+34,middle_right,&fonts::FreeSans9pt7b,YELLOW);}label("CYAN = work done YELLOW = measured time Tap for menu",640,688,middle_center,&fonts::FreeSans9pt7b,MUTED);while(true){M5.update();if(M5.Touch.getDetail().wasReleased())break;delay(12);}}
void shuffle(){for(int i=0;i<N;i++)base[i]=i+1;for(int i=N-1;i>0;i--){int j=random(i+1);int v=base[i];base[i]=base[j];base[j]=v;}} void shuffle(){for(int i=0;i<N;i++)base[i]=i+1;for(int i=N-1;i>0;i--){int j=random(i+1);int v=base[i];base[i]=base[j];base[j]=v;}}
void menu(){M5.Display.fillScreen(BG);M5.Display.fillRect(0,0,1280,102,PANEL);label("SORTING LAB",34,42,middle_left,&fonts::FreeSansBold24pt7b,TFT_WHITE,PANEL);label("Choose one, or run all on the same shuffled data",1244,42,middle_right,&fonts::FreeSans12pt7b,MUTED,PANEL);for(int i=0;i<8;i++){int col=i%2,row=i/2;Rect r{34+col*614,126+row*112,596,94};uint16_t fill=i==which?0x2B8E:PANEL2;M5.Display.fillRoundRect(r.x,r.y,r.w,r.h,16,fill);M5.Display.drawRoundRect(r.x,r.y,r.w,r.h,16,0x31A6);label(alg[i].name,r.x+24,r.y+31,middle_left,&fonts::FreeSansBold12pt7b,TFT_WHITE,fill);label(alg[i].lesson,r.x+24,r.y+67,middle_left,&fonts::FreeSans9pt7b,MUTED,fill);label(alg[i].cost,r.x+r.w-18,r.y+31,middle_right,&fonts::FreeSansBold12pt7b,YELLOW,fill);}Rect sh{34,598,250,78},fast{302,598,370,78},all{690,598,556,78};button(sh,"NEW SHUFFLE",0x620B);button(fast,"NO-VISUAL BENCHMARK",YELLOW);button(all,"RUN ALL 8",0x4A2E);while(true){M5.update();auto t=M5.Touch.getDetail();if(t.wasReleased()){if(sh.has(t.x,t.y)){shuffle();menu();return;}if(fast.has(t.x,t.y)){benchmark=true;runAll=false;return;}if(all.has(t.x,t.y)){which=0;runAll=true;benchmark=false;for(auto&r:results)r=Result{};return;}for(int i=0;i<8;i++){Rect r{34+(i%2)*614,126+(i/2)*112,596,94};if(r.has(t.x,t.y)){which=i;runAll=false;benchmark=false;return;}}}delay(12);}} void menu(){M5.Display.fillScreen(BG);M5.Display.fillRect(0,0,1280,102,PANEL);label("SORTING LAB",34,42,middle_left,&fonts::FreeSansBold24pt7b,TFT_WHITE,PANEL);label("Choose one, or compare all eight",1244,42,middle_right,&fonts::FreeSans12pt7b,MUTED,PANEL);for(int i=0;i<8;i++){int col=i%2,row=i/2;Rect r{34+col*614,126+row*112,596,94};uint16_t fill=i==which?0x2B8E:PANEL2;M5.Display.fillRoundRect(r.x,r.y,r.w,r.h,16,fill);M5.Display.drawRoundRect(r.x,r.y,r.w,r.h,16,0x31A6);label(alg[i].name,r.x+24,r.y+31,middle_left,&fonts::FreeSansBold12pt7b,TFT_WHITE,fill);label(alg[i].lesson,r.x+24,r.y+67,middle_left,&fonts::FreeSans9pt7b,MUTED,fill);label(alg[i].cost,r.x+r.w-18,r.y+31,middle_right,&fonts::FreeSansBold12pt7b,YELLOW,fill);}Rect sh{34,598,238,78},oneFast{288,598,278,78},all{582,598,316,78},allFast{914,598,332,78};button(sh,"NEW SHUFFLE",0x620B);button(oneFast,"SELECTED FAST",YELLOW);button(all,"ALL VISUAL",0x4A2E);button(allFast,"ALL FAST",0x6A2E);while(true){M5.update();auto t=M5.Touch.getDetail();if(t.wasReleased()){if(sh.has(t.x,t.y)){shuffle();menu();return;}if(oneFast.has(t.x,t.y)){benchmark=true;runAll=false;return;}if(all.has(t.x,t.y)){which=0;runAll=true;benchmark=false;for(auto&r:results)r=Result{};return;}if(allFast.has(t.x,t.y)){which=0;runAll=true;benchmark=true;for(auto&r:results)r=Result{};return;}for(int i=0;i<8;i++){Rect r{34+(i%2)*614,126+(i/2)*112,596,94};if(r.has(t.x,t.y)){which=i;runAll=false;benchmark=false;return;}}}delay(12);}}
} }
void setup(){Serial.begin(115200);auto cfg=M5.config();M5.begin(cfg);M5.Display.setRotation(1);M5.Display.setColorDepth(16);M5.Display.setBrightness(180);barCanvas.setColorDepth(16);barCanvas.setPsram(true);barCanvas.createSprite(CW,CH);randomSeed(esp_random());shuffle();Serial.println("Tab5 Sorting Lab ready");} void setup(){Serial.begin(115200);auto cfg=M5.config();M5.begin(cfg);M5.Display.setRotation(1);M5.Display.setColorDepth(16);M5.Display.setBrightness(180);barCanvas.setColorDepth(16);barCanvas.setPsram(true);barCanvas.createSprite(CW,CH);randomSeed(esp_random());shuffle();Serial.println("Tab5 Sorting Lab ready");}
void loop(){menu();do{reset();runScreen();startMs=millis();startUs=micros();bool done=sortNow();if(done){uint32_t elapsedUs=benchmark?(micros()-startUs):(millis()-startMs-pauseTotal)*1000UL;results[which]={cmp,moves,elapsedUs,true};result();Serial.printf("%s: %lu comparisons, %lu moves, %.3f ms%s\n",alg[which].name,(unsigned long)cmp,(unsigned long)moves,elapsedUs/1000.0f,benchmark?" (no visual)":"");if(runAll){if(waitFor(1700)&&which<7){which++;continue;}if(which==7){summary();runAll=false;}break;}waitFor(10000);break;}bool again=redo;abortRun=false;if(again)continue;break;}while(true);} void loop(){menu();do{reset();runScreen();startMs=millis();startUs=micros();bool done=sortNow();if(done){uint32_t elapsedUs=benchmark?(micros()-startUs):(millis()-startMs-pauseTotal)*1000UL;results[which]={cmp,moves,elapsedUs,true};Serial.printf("%s: %lu comparisons, %lu moves, %.3f ms%s\n",alg[which].name,(unsigned long)cmp,(unsigned long)moves,elapsedUs/1000.0f,benchmark?" (no visual)":"");if(runAll&&benchmark){if(which<7){which++;continue;}summary();runAll=false;break;}result();if(runAll){if(waitFor(700)&&which<7){which++;continue;}if(which==7){summary();runAll=false;}break;}waitFor(10000);break;}bool again=redo;abortRun=false;if(again)continue;break;}while(true);}