From cea46573c86fd24df8350006d749f34c1d1a9ec2 Mon Sep 17 00:00:00 2001 From: helios Date: Sun, 9 Aug 2026 15:49:58 +0200 Subject: [PATCH] Add interactive Tab5 sorting algorithm visualizer --- .gitignore | 2 ++ README.md | 9 +++++++++ platformio.ini | 16 +++++++++++++++ src/main.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 platformio.ini create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..968a41b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.pio/ +.vscode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e2102bc --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# M5Stack Tab5 Sorting Lab + +Touch-controlled visual guide to Bubble, Insertion, Selection, Shell, Merge, +Quick, Heap, and Radix sort. Each algorithm animates the same 48 shuffled +values and reports comparisons, moves, and elapsed time. **RUN ALL 8** ends +with a fair operation-count chart. + +Controls: **Pause/Resume**, **Redo**, **Next**, **Run All 8**, and +**Algorithm Menu**. Build with `pio run`, upload with `pio run -t upload`. diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..31e24b0 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,16 @@ +[env:tab5] +platform = https://github.com/pioarduino/platform-espressif32.git#54.03.21 +board = esp32-p4-evboard +framework = arduino +monitor_speed = 115200 +upload_speed = 1500000 +board_build.mcu = esp32p4 +board_build.flash_mode = qio +build_flags = + -DBOARD_HAS_PSRAM + -DCORE_DEBUG_LEVEL=3 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_MODE=1 +lib_deps = + https://github.com/M5Stack/M5Unified.git + https://github.com/M5Stack/M5GFX.git diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ffca835 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,53 @@ +#include +#include + +namespace { +constexpr int N=48, CX=34, CY=142, CW=1212, CH=450; +constexpr uint16_t BG=0x0841,PANEL=0x18E3,PANEL2=0x2125,MUTED=0xBDF7,CYAN=0x2E9F,YELLOW=0xFDC0,GREEN=0x4EE9; +struct Rect{int x,y,w,h;bool has(int px,int py)const{return px>=x&&px=y&&py0);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++;} + +bool bubble(){for(int end=N-1;end>0;end--){bool changed=false;for(int i=0;ia[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=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;i0;gap/=2)for(int i=gap;i=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 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[big])big=l;}if(ra[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 radix(){for(int exp=1;N/exp>0;exp*=10){int count[10]={0};for(int i=0;i=0;i--){tmp[--count[(a[i]/exp)%10]]=a[i];moves++;}for(int i=0;i0;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};M5.Display.fillRoundRect(r.x,r.y,r.w,r.h,16,PANEL2);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,PANEL2);label(alg[i].lesson,r.x+24,r.y+67,middle_left,&fonts::FreeSans9pt7b,MUTED,PANEL2);label(alg[i].cost,r.x+r.w-18,r.y+31,middle_right,&fonts::FreeSansBold12pt7b,YELLOW,PANEL2);}Rect sh{34,598,300,78},all{352,598,894,78};button(sh,"NEW SHUFFLE",0x620B);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(all.has(t.x,t.y)){which=0;runAll=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;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);randomSeed(esp_random());shuffle();Serial.println("Tab5 Sorting Lab ready");} +void loop(){menu();do{reset();runScreen();startMs=millis();bool done=sortNow();if(done){uint32_t elapsed=millis()-startMs-pauseTotal;results[which]={cmp,moves,elapsed,true};result();Serial.printf("%s: %lu comparisons, %lu moves, %lu ms\n",alg[which].name,(unsigned long)cmp,(unsigned long)moves,(unsigned long)elapsed);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);}