ft-ps-visu 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Italo Almeida
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.4
2
+ Name: ft_ps_visu
3
+ Version: 1.0.0
4
+ Summary: A terminal visualizer for the 42 push_swap project with real-time TUI, controlled disorder generation, and interactive playback.
5
+ Author: Italo Almeida
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/italoalmeida0/ft_ps_visu
8
+ Project-URL: Issues, https://github.com/italoalmeida0/ft_ps_visu/issues
9
+ Keywords: 42,push_swap,visualizer,terminal,tui,sorting,algorithm
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.7
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Education :: Testing
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Classifier: Topic :: Terminals
23
+ Requires-Python: >=3.7
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Dynamic: license-file
27
+
28
+ # ft_ps_visu
29
+
30
+ A terminal visualizer for the **42 push_swap** project. It generates controlled random sequences with specific disorder levels, runs your `push_swap` executable, and renders a real-time TUI (Terminal User Interface) with interactive playback controls.
31
+
32
+ ---
33
+ <img src="i1.png" width="500">
34
+ ---
35
+
36
+ ## Features
37
+
38
+ - **Real-time TUI visualization** — watch your `push_swap` algorithm sort stacks live in the terminal.
39
+ - **Controlled disorder generation** — creates sequences with precise inversion percentages.
40
+ - **Four test modes** — adaptive, simple, medium, and complex.
41
+ - **Interactive controls** — play/pause, forward/reverse, step-by-step, speed adjustment, and more.
42
+ - **True-color bars** — gradient-colored bars representing values for easy visual tracking.
43
+ - **Responsive layout** — adapts to terminal resize events.
44
+
45
+ ---
46
+
47
+ ## Requirements
48
+
49
+ - Python 3.7+
50
+ - A compiled `push_swap` executable that accepts a `--<mode>` flag and the numbers to sort
51
+ - A terminal that supports:
52
+ - ANSI escape codes
53
+ - True-color (24-bit RGB) for the best experience
54
+ - Alternate screen buffer (`\033[?1049h` / `\033[?1049l`)
55
+
56
+ > **Note:** On Windows, use Windows Terminal, WSL, or any modern terminal emulator. The classic `cmd.exe` console may not render Unicode block characters or true-color correctly.
57
+
58
+ ---
59
+
60
+ ## Installation
61
+
62
+ > **Tip:** If you run into installation errors, try updating `pip` first:
63
+ > ```bash
64
+ > pip install --upgrade pip
65
+ > # or
66
+ > pip3 install --upgrade pip
67
+ > ```
68
+
69
+ ### Option 1: Install from PyPI (recommended)
70
+
71
+ Using `pip`:
72
+
73
+ ```bash
74
+ pip install ft_ps_visu
75
+ ```
76
+
77
+ Using `pip3`:
78
+
79
+ ```bash
80
+ pip3 install ft_ps_visu
81
+ ```
82
+
83
+ User-local install (no sudo required — `pip`):
84
+
85
+ ```bash
86
+ pip install --user ft_ps_visu
87
+ ```
88
+
89
+ Using `python3 -m pip`:
90
+
91
+ ```bash
92
+ python3 -m pip install ft_ps_visu
93
+ ```
94
+
95
+ > **Note:** When using `--user`, the `ft_ps_visu` binary is installed to a user-local `bin/` directory. Make sure this directory is on your `PATH`, or use the `python3 -m` execution methods shown below.
96
+
97
+ ---
98
+
99
+ ### Option 2: Install from source
100
+
101
+ Clone this repository:
102
+
103
+ ```bash
104
+ git clone https://github.com/italoalmeida0/ft_ps_visu.git
105
+ cd ft_ps_visu
106
+ ```
107
+
108
+ Editable / development mode (`pip`):
109
+
110
+ ```bash
111
+ pip install -e .
112
+ ```
113
+
114
+ Normal install (`pip`):
115
+
116
+ ```bash
117
+ pip install .
118
+ ```
119
+
120
+ ---
121
+
122
+ ### Option 3: Run with `pipx` (isolated, no install required)
123
+
124
+ If you have [`pipx`](https://pypa.github.io/pipx/) installed, you can run the visualizer directly without permanently installing it:
125
+
126
+ ```bash
127
+ pipx run ft_ps_visu ./push_swap
128
+ ```
129
+
130
+ Or install it into an isolated environment:
131
+
132
+ ```bash
133
+ pipx install ft_ps_visu
134
+ ```
135
+
136
+ Then run normally:
137
+
138
+ ```bash
139
+ ft_ps_visu ./push_swap
140
+ ```
141
+
142
+ ---
143
+
144
+ Make sure your `push_swap` binary is compiled and executable:
145
+
146
+ ```bash
147
+ make
148
+ chmod +x push_swap
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Usage
154
+
155
+ ### Basic usage
156
+
157
+ ```bash
158
+ ft_ps_visu ./push_swap
159
+ ```
160
+
161
+ With custom number of elements:
162
+
163
+ ```bash
164
+ ft_ps_visu ./push_swap 100
165
+ ```
166
+
167
+ With custom disorder percentage (0–55):
168
+
169
+ ```bash
170
+ ft_ps_visu ./push_swap 500 30
171
+ ```
172
+
173
+ If `ft_ps_visu` is **not** found on your `PATH`, run via the module:
174
+
175
+ ```bash
176
+ python3 -m ft_ps_visu ./push_swap
177
+ ```
178
+
179
+ Or using the `.cli` submodule directly:
180
+
181
+ ```bash
182
+ python3 -m ft_ps_visu.cli ./push_swap
183
+ ```
184
+
185
+ From the cloned source directory (no install required):
186
+
187
+ ```bash
188
+ python3 ft_ps_visu/cli.py ./push_swap
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Controls
194
+
195
+ | Key | Action |
196
+ |-----|--------|
197
+ | `P` | Play / Pause |
198
+ | `O` | Toggle Forward / Reverse direction |
199
+ | `N` | Next step (forward one operation) |
200
+ | `B` | Back step (reverse one operation) |
201
+ | `X` | Increase speed |
202
+ | `Z` | Decrease speed |
203
+ | `G` | Re-generate data with current settings |
204
+ | `M` | Cycle through modes (adaptive → simple → medium → complex) |
205
+ | `A` | Decrease number of elements |
206
+ | `S` | Increase number of elements |
207
+ | `D` | Decrease disorder percentage |
208
+ | `F` | Increase disorder percentage |
209
+ | `Q` | Quit |
210
+
211
+ ---
212
+
213
+ ## Modes / Flags
214
+
215
+ Your `push_swap` must support the following flags (passed as `--<mode>` before the numbers):
216
+
217
+ | Mode | Disorder range | Description |
218
+ |------------|----------------|------------------------------------------|
219
+ | `simple` | 15.0% – 19.9% | Nearly sorted sequences |
220
+ | `medium` | 20.0% – 49.9% | Moderately shuffled sequences |
221
+ | `complex` | 50.0% – 55.0% | Heavily shuffled sequences |
222
+ | `adaptive` | 15.0% – 55.0% | Random disorder across the full spectrum |
223
+
224
+ > **Note:** If your `push_swap` does **not** implement these flags, the visualizer will still work if your program ignores unknown flags and simply sorts the provided numbers.
225
+
226
+ ---
227
+
228
+ ## How it works
229
+
230
+ 1. **Generate** a random sequence with the desired size and disorder level.
231
+ 2. **Run** your `push_swap` executable with the sequence.
232
+ 3. **Capture** the operations printed to `stdout`.
233
+ 4. **Render** a TUI showing both stacks as colored bars.
234
+ 5. **Animate** the operations at the chosen speed, allowing forward and reverse playback.
235
+
236
+ ---
237
+
238
+ ## License
239
+
240
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,213 @@
1
+ # ft_ps_visu
2
+
3
+ A terminal visualizer for the **42 push_swap** project. It generates controlled random sequences with specific disorder levels, runs your `push_swap` executable, and renders a real-time TUI (Terminal User Interface) with interactive playback controls.
4
+
5
+ ---
6
+ <img src="i1.png" width="500">
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - **Real-time TUI visualization** — watch your `push_swap` algorithm sort stacks live in the terminal.
12
+ - **Controlled disorder generation** — creates sequences with precise inversion percentages.
13
+ - **Four test modes** — adaptive, simple, medium, and complex.
14
+ - **Interactive controls** — play/pause, forward/reverse, step-by-step, speed adjustment, and more.
15
+ - **True-color bars** — gradient-colored bars representing values for easy visual tracking.
16
+ - **Responsive layout** — adapts to terminal resize events.
17
+
18
+ ---
19
+
20
+ ## Requirements
21
+
22
+ - Python 3.7+
23
+ - A compiled `push_swap` executable that accepts a `--<mode>` flag and the numbers to sort
24
+ - A terminal that supports:
25
+ - ANSI escape codes
26
+ - True-color (24-bit RGB) for the best experience
27
+ - Alternate screen buffer (`\033[?1049h` / `\033[?1049l`)
28
+
29
+ > **Note:** On Windows, use Windows Terminal, WSL, or any modern terminal emulator. The classic `cmd.exe` console may not render Unicode block characters or true-color correctly.
30
+
31
+ ---
32
+
33
+ ## Installation
34
+
35
+ > **Tip:** If you run into installation errors, try updating `pip` first:
36
+ > ```bash
37
+ > pip install --upgrade pip
38
+ > # or
39
+ > pip3 install --upgrade pip
40
+ > ```
41
+
42
+ ### Option 1: Install from PyPI (recommended)
43
+
44
+ Using `pip`:
45
+
46
+ ```bash
47
+ pip install ft_ps_visu
48
+ ```
49
+
50
+ Using `pip3`:
51
+
52
+ ```bash
53
+ pip3 install ft_ps_visu
54
+ ```
55
+
56
+ User-local install (no sudo required — `pip`):
57
+
58
+ ```bash
59
+ pip install --user ft_ps_visu
60
+ ```
61
+
62
+ Using `python3 -m pip`:
63
+
64
+ ```bash
65
+ python3 -m pip install ft_ps_visu
66
+ ```
67
+
68
+ > **Note:** When using `--user`, the `ft_ps_visu` binary is installed to a user-local `bin/` directory. Make sure this directory is on your `PATH`, or use the `python3 -m` execution methods shown below.
69
+
70
+ ---
71
+
72
+ ### Option 2: Install from source
73
+
74
+ Clone this repository:
75
+
76
+ ```bash
77
+ git clone https://github.com/italoalmeida0/ft_ps_visu.git
78
+ cd ft_ps_visu
79
+ ```
80
+
81
+ Editable / development mode (`pip`):
82
+
83
+ ```bash
84
+ pip install -e .
85
+ ```
86
+
87
+ Normal install (`pip`):
88
+
89
+ ```bash
90
+ pip install .
91
+ ```
92
+
93
+ ---
94
+
95
+ ### Option 3: Run with `pipx` (isolated, no install required)
96
+
97
+ If you have [`pipx`](https://pypa.github.io/pipx/) installed, you can run the visualizer directly without permanently installing it:
98
+
99
+ ```bash
100
+ pipx run ft_ps_visu ./push_swap
101
+ ```
102
+
103
+ Or install it into an isolated environment:
104
+
105
+ ```bash
106
+ pipx install ft_ps_visu
107
+ ```
108
+
109
+ Then run normally:
110
+
111
+ ```bash
112
+ ft_ps_visu ./push_swap
113
+ ```
114
+
115
+ ---
116
+
117
+ Make sure your `push_swap` binary is compiled and executable:
118
+
119
+ ```bash
120
+ make
121
+ chmod +x push_swap
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Usage
127
+
128
+ ### Basic usage
129
+
130
+ ```bash
131
+ ft_ps_visu ./push_swap
132
+ ```
133
+
134
+ With custom number of elements:
135
+
136
+ ```bash
137
+ ft_ps_visu ./push_swap 100
138
+ ```
139
+
140
+ With custom disorder percentage (0–55):
141
+
142
+ ```bash
143
+ ft_ps_visu ./push_swap 500 30
144
+ ```
145
+
146
+ If `ft_ps_visu` is **not** found on your `PATH`, run via the module:
147
+
148
+ ```bash
149
+ python3 -m ft_ps_visu ./push_swap
150
+ ```
151
+
152
+ Or using the `.cli` submodule directly:
153
+
154
+ ```bash
155
+ python3 -m ft_ps_visu.cli ./push_swap
156
+ ```
157
+
158
+ From the cloned source directory (no install required):
159
+
160
+ ```bash
161
+ python3 ft_ps_visu/cli.py ./push_swap
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Controls
167
+
168
+ | Key | Action |
169
+ |-----|--------|
170
+ | `P` | Play / Pause |
171
+ | `O` | Toggle Forward / Reverse direction |
172
+ | `N` | Next step (forward one operation) |
173
+ | `B` | Back step (reverse one operation) |
174
+ | `X` | Increase speed |
175
+ | `Z` | Decrease speed |
176
+ | `G` | Re-generate data with current settings |
177
+ | `M` | Cycle through modes (adaptive → simple → medium → complex) |
178
+ | `A` | Decrease number of elements |
179
+ | `S` | Increase number of elements |
180
+ | `D` | Decrease disorder percentage |
181
+ | `F` | Increase disorder percentage |
182
+ | `Q` | Quit |
183
+
184
+ ---
185
+
186
+ ## Modes / Flags
187
+
188
+ Your `push_swap` must support the following flags (passed as `--<mode>` before the numbers):
189
+
190
+ | Mode | Disorder range | Description |
191
+ |------------|----------------|------------------------------------------|
192
+ | `simple` | 15.0% – 19.9% | Nearly sorted sequences |
193
+ | `medium` | 20.0% – 49.9% | Moderately shuffled sequences |
194
+ | `complex` | 50.0% – 55.0% | Heavily shuffled sequences |
195
+ | `adaptive` | 15.0% – 55.0% | Random disorder across the full spectrum |
196
+
197
+ > **Note:** If your `push_swap` does **not** implement these flags, the visualizer will still work if your program ignores unknown flags and simply sorts the provided numbers.
198
+
199
+ ---
200
+
201
+ ## How it works
202
+
203
+ 1. **Generate** a random sequence with the desired size and disorder level.
204
+ 2. **Run** your `push_swap` executable with the sequence.
205
+ 3. **Capture** the operations printed to `stdout`.
206
+ 4. **Render** a TUI showing both stacks as colored bars.
207
+ 5. **Animate** the operations at the chosen speed, allowing forward and reverse playback.
208
+
209
+ ---
210
+
211
+ ## License
212
+
213
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,3 @@
1
+ """ft_ps_visu - A terminal visualizer for the 42 push_swap project."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,4 @@
1
+ from ft_ps_visu.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,536 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ import os
5
+ import random
6
+ import subprocess
7
+ import signal
8
+ import select
9
+ import termios
10
+ import tty
11
+ import shutil
12
+ from collections import deque
13
+
14
+ # ==========================================
15
+ # CONFIGURATION & INITIALIZATION
16
+ # ==========================================
17
+ def parse_arguments():
18
+ if len(sys.argv) < 2 or len(sys.argv) > 4:
19
+ print(f"Usage: {sys.argv[0]} <path_to_push_swap> [number_of_elements] [max_disorder_percentage]")
20
+ sys.exit(1)
21
+
22
+ target_executable = sys.argv[1]
23
+ if not os.path.isfile(target_executable) or not os.access(target_executable, os.X_OK):
24
+ print(f"Error: '{target_executable}' is not a valid or executable file.")
25
+ sys.exit(1)
26
+
27
+ n_elems = 500
28
+ if len(sys.argv) >= 3:
29
+ try:
30
+ n_elems = int(sys.argv[2])
31
+ if n_elems <= 0:
32
+ raise ValueError
33
+ except ValueError:
34
+ print("Error: number_of_elements must be a positive integer.")
35
+ sys.exit(1)
36
+
37
+ max_disorder = 50
38
+ if len(sys.argv) == 4:
39
+ try:
40
+ max_disorder = int(sys.argv[3])
41
+ if max_disorder < 0 or max_disorder > 55:
42
+ raise ValueError
43
+ except ValueError:
44
+ print("Error: max_disorder_percentage must be between 0 and 55.")
45
+ sys.exit(1)
46
+
47
+ return target_executable, n_elems, max_disorder
48
+
49
+ # ==========================================
50
+ # TERMINAL CONTEXT MANAGER
51
+ # ==========================================
52
+ class TerminalTUI:
53
+ def __init__(self):
54
+ self.fd = sys.stdin.fileno()
55
+ self.old_settings = None
56
+
57
+ def __enter__(self):
58
+ self.old_settings = termios.tcgetattr(self.fd)
59
+ tty.setcbreak(self.fd)
60
+ sys.stdout.write("\033[?1049h\033[?25l")
61
+ sys.stdout.flush()
62
+ return self
63
+
64
+ def __exit__(self, exc_type, exc_val, exc_tb):
65
+ sys.stdout.write("\033[0m\033[?25h\033[?1049l")
66
+ sys.stdout.flush()
67
+ termios.tcsetattr(self.fd, termios.TCSADRAIN, self.old_settings)
68
+
69
+ def get_key(timeout):
70
+ r, _, _ = select.select([sys.stdin], [], [], timeout)
71
+ if r:
72
+ data = os.read(sys.stdin.fileno(), 3)
73
+ if len(data) >= 1:
74
+ return data.decode('utf-8', 'ignore')
75
+ return None
76
+
77
+ # ==========================================
78
+ # VISUALIZER ENGINE
79
+ # ==========================================
80
+ class PushSwapVisualizer:
81
+ def __init__(self, target_executable, n_elems, max_disorder):
82
+ self.target_executable = target_executable
83
+ self.n_elems = n_elems
84
+ self.disorder = max_disorder
85
+ self.actual_disorder = 0.0
86
+
87
+ self.allowed_sizes = [10, 50, 100, 200, 500, 1000]
88
+ if self.n_elems not in self.allowed_sizes:
89
+ self.allowed_sizes.append(self.n_elems)
90
+ self.allowed_sizes.sort()
91
+
92
+ self.stack_a = deque()
93
+ self.stack_b = deque()
94
+ self.ops = []
95
+ self.total_ops = 0
96
+ self.op_idx = 0
97
+
98
+ # New mode/flags integration
99
+ self.flags = ["--adaptive", "--simple", "--medium", "--complex"]
100
+ self.flag_idx = 0
101
+
102
+ self.force_redraw = True
103
+ self.auto_play = False
104
+ self.play_dir = "FWD"
105
+
106
+ self.fps = 30
107
+ self.frame_delay = 1.0 / self.fps
108
+ self.speeds = [1, 5, 10, 30, 60, 150, 300, 600, 1200, 3000, 6000, 12000, 50000, 100000]
109
+ self.speed_idx = 5
110
+ self.accumulator = 0
111
+
112
+ def compute_disorder(self, sequence):
113
+ n = len(sequence)
114
+ if n < 2:
115
+ return 0.0
116
+
117
+ def count_inversions(arr):
118
+ if len(arr) <= 1:
119
+ return arr, 0
120
+ mid = len(arr) // 2
121
+ left, inv_left = count_inversions(arr[:mid])
122
+ right, inv_right = count_inversions(arr[mid:])
123
+ merged, inv_merge = merge(left, right)
124
+ return merged, inv_left + inv_right + inv_merge
125
+
126
+ def merge(left, right):
127
+ merged = []
128
+ inv_count = 0
129
+ i, j = 0, 0
130
+ while i < len(left) and j < len(right):
131
+ if left[i] <= right[j]:
132
+ merged.append(left[i])
133
+ i += 1
134
+ else:
135
+ merged.append(right[j])
136
+ inv_count += len(left) - i
137
+ j += 1
138
+ merged += left[i:]
139
+ merged += right[j:]
140
+ return merged, inv_count
141
+
142
+ _, mistakes = count_inversions(list(sequence))
143
+ total_pairs = (n * (n - 1)) / 2.0
144
+
145
+ return (mistakes / total_pairs) * 100.0
146
+
147
+ def generate_data(self):
148
+ self.auto_play = False
149
+ self.op_idx = 0
150
+ self.accumulator = 0
151
+ self.ops = []
152
+ self.total_ops = 0
153
+
154
+ sys.stdout.write("\033[2J\033[H\033[1;36mGenerating data and running push_swap... Please wait.\033[0m\r\n")
155
+ sys.stdout.flush()
156
+
157
+ raw_sequence = random.sample(range(-1000000, 1000000), self.n_elems)
158
+ raw_sequence.sort()
159
+
160
+ n = self.n_elems
161
+ total_pairs = (n * (n - 1)) / 2.0
162
+ target_inv = int((self.disorder / 100.0) * total_pairs)
163
+
164
+ if target_inv > 0:
165
+ inv = [0] * n
166
+ indices = list(range(n))
167
+ random.shuffle(indices)
168
+
169
+ remaining = target_inv
170
+ for i in indices:
171
+ max_cap = n - 1 - i
172
+ take = random.randint(0, min(remaining, max_cap))
173
+ inv[i] = take
174
+ remaining -= take
175
+
176
+ if remaining > 0:
177
+ random.shuffle(indices)
178
+ for i in indices:
179
+ max_cap = n - 1 - i
180
+ space = max_cap - inv[i]
181
+ if space > 0:
182
+ take = min(remaining, space)
183
+ inv[i] += take
184
+ remaining -= take
185
+ if remaining == 0:
186
+ break
187
+
188
+ result_sequence = []
189
+ for i in range(n - 1, -1, -1):
190
+ val = raw_sequence[i]
191
+ insert_pos = inv[i]
192
+ result_sequence.insert(insert_pos, val)
193
+
194
+ raw_sequence = result_sequence
195
+
196
+ self.actual_disorder = self.compute_disorder(raw_sequence)
197
+
198
+ str_seq = [str(x) for x in raw_sequence]
199
+ current_flag = self.flags[self.flag_idx]
200
+
201
+
202
+ result = subprocess.run(
203
+ [self.target_executable, current_flag] + str_seq,
204
+ capture_output=True,
205
+ text=True,
206
+ check=False
207
+ )
208
+ self.ops = result.stdout.strip().split()
209
+ self.total_ops = len(self.ops)
210
+
211
+
212
+ sorted_seq = sorted(raw_sequence)
213
+ rank_map = {val: i + 1 for i, val in enumerate(sorted_seq)}
214
+ ranks_sequence = [rank_map[val] for val in raw_sequence]
215
+
216
+ self.stack_a = deque(ranks_sequence)
217
+ self.stack_b = deque()
218
+ self.force_redraw = True
219
+
220
+ def handle_resize(self, signum, frame):
221
+ self.force_redraw = True
222
+
223
+ def exec_op(self, op):
224
+ if op == "sa" and len(self.stack_a) >= 2:
225
+ self.stack_a[0], self.stack_a[1] = self.stack_a[1], self.stack_a[0]
226
+ elif op == "sb" and len(self.stack_b) >= 2:
227
+ self.stack_b[0], self.stack_b[1] = self.stack_b[1], self.stack_b[0]
228
+ elif op == "ss":
229
+ self.exec_op("sa"); self.exec_op("sb")
230
+ elif op == "pa" and len(self.stack_b) >= 1:
231
+ self.stack_a.appendleft(self.stack_b.popleft())
232
+ elif op == "pb" and len(self.stack_a) >= 1:
233
+ self.stack_b.appendleft(self.stack_a.popleft())
234
+ elif op == "ra" and len(self.stack_a) >= 2:
235
+ self.stack_a.append(self.stack_a.popleft())
236
+ elif op == "rb" and len(self.stack_b) >= 2:
237
+ self.stack_b.append(self.stack_b.popleft())
238
+ elif op == "rr":
239
+ self.exec_op("ra"); self.exec_op("rb")
240
+ elif op == "rra" and len(self.stack_a) >= 2:
241
+ self.stack_a.appendleft(self.stack_a.pop())
242
+ elif op == "rrb" and len(self.stack_b) >= 2:
243
+ self.stack_b.appendleft(self.stack_b.pop())
244
+ elif op == "rrr":
245
+ self.exec_op("rra"); self.exec_op("rrb")
246
+
247
+ def exec_inv_op(self, op):
248
+ inv_map = {
249
+ "sa": "sa", "sb": "sb", "ss": "ss",
250
+ "pa": "pb", "pb": "pa",
251
+ "ra": "rra", "rb": "rrb", "rr": "rrr",
252
+ "rra": "ra", "rrb": "rb", "rrr": "rr"
253
+ }
254
+ if op in inv_map:
255
+ self.exec_op(inv_map[op])
256
+
257
+ def get_rgb(self, val, max_val):
258
+ if val <= 0: return "0;0;0"
259
+ ratio = val / max_val
260
+ if ratio < 0.25:
261
+ return f"0;{int((ratio/0.25)*255)};255"
262
+ elif ratio < 0.5:
263
+ return f"0;255;{int((1-(ratio-0.25)/0.25)*255)}"
264
+ elif ratio < 0.75:
265
+ return f"{int(((ratio-0.5)/0.25)*255)};255;0"
266
+ else:
267
+ return f"255;{int((1-(ratio-0.75)/0.25)*255)};0"
268
+
269
+ def build_bar(self, val1, val2, max_w):
270
+ if val1 == -1 and val2 == -1:
271
+ return " " * max_w
272
+
273
+ l1 = int((val1 * max_w) / self.n_elems) if val1 > 0 else 0
274
+ l2 = int((val2 * max_w) / self.n_elems) if val2 > 0 else 0
275
+
276
+ if val1 > 0 and l1 == 0: l1 = 1
277
+ if val2 > 0 and l2 == 0: l2 = 1
278
+
279
+ rgb1 = self.get_rgb(val1, self.n_elems)
280
+ rgb2 = self.get_rgb(val2, self.n_elems)
281
+
282
+ min_l = min(l1, l2)
283
+ max_l = max(l1, l2)
284
+
285
+ out = ""
286
+ if min_l > 0:
287
+ out += f"\033[38;2;{rgb1}m\033[48;2;{rgb2}m" + ("▀" * min_l) + "\033[0m"
288
+
289
+ if l1 > l2:
290
+ out += f"\033[38;2;{rgb1}m\033[49m" + ("▀" * (l1 - min_l)) + "\033[0m"
291
+ elif l2 > l1:
292
+ out += f"\033[38;2;{rgb2}m\033[49m" + ("▄" * (l2 - min_l)) + "\033[0m"
293
+
294
+ spaces = max_w - max_l
295
+ if spaces > 0:
296
+ out += " " * spaces
297
+
298
+ return out
299
+
300
+ def layout_items(self, items, default_chunk, cols):
301
+ chunks = [items[i:i + default_chunk] for i in range(0, len(items), default_chunk)]
302
+ fits = all(sum(it[0] for it in chunk) + (len(chunk) - 1) * 3 + 2 <= cols - 2 for chunk in chunks)
303
+
304
+ if fits:
305
+ return chunks
306
+
307
+ for chunk_size in range(4, 0, -1):
308
+ chunks = [items[i:i + chunk_size] for i in range(0, len(items), chunk_size)]
309
+ fits = all(sum(it[0] for it in chunk) + (len(chunk) - 1) * 3 + 2 <= cols - 2 for chunk in chunks)
310
+ if fits:
311
+ return chunks
312
+
313
+ return [[item] for item in items]
314
+
315
+ def sample_stack(self, stack, m_slots):
316
+ length = len(stack)
317
+ if length == 0: return []
318
+ if length <= m_slots: return list(stack)
319
+ return [stack[int(k * (length - 1) / (m_slots - 1))] if m_slots > 1 else stack[0] for k in range(m_slots)]
320
+
321
+ def draw_screen(self):
322
+ cols, lines = shutil.get_terminal_size()
323
+
324
+ clear_cmd = "\033[H"
325
+ if self.force_redraw:
326
+ clear_cmd = "\033[2J\033[H"
327
+ self.force_redraw = False
328
+
329
+ c_rst = "\033[0m"; c_dim = "\033[2m"; c_bold = "\033[1m"
330
+ c_cyan = "\033[1;36m"; c_yellow = "\033[1;33m"; c_green = "\033[1;32m"
331
+ c_red = "\033[1;31m"; c_magenta = "\033[1;35m"; c_frame = "\033[38;5;60m"
332
+
333
+ speed_val = self.speeds[self.speed_idx]
334
+ auto_str = "ON" if self.auto_play else "OFF"
335
+ auto_col = c_green if self.auto_play else c_red
336
+ dir_col = c_cyan if self.play_dir == "FWD" else c_magenta
337
+ mode_str = self.flags[self.flag_idx]
338
+
339
+ top_items = [
340
+ (len("push_swap visualizer"), f"{c_cyan}push_swap visualizer{c_rst}"),
341
+ (len(f"Nums: {self.n_elems}"), f"{c_yellow}Nums: {self.n_elems}{c_rst}"),
342
+ (len(f"Mode: {mode_str}"), f"{c_green}Mode: {mode_str}{c_rst}"),
343
+ (len(f"Disorder: {self.actual_disorder:.1f}%"), f"{c_magenta}Disorder: {self.actual_disorder:.1f}%{c_rst}"),
344
+ (len(f"Ops: {self.op_idx}/{self.total_ops}"), f"{c_bold}Ops: {self.op_idx}/{self.total_ops}{c_rst}"),
345
+ (len(f"Auto: {auto_str} ({self.play_dir})"), f"Auto: {auto_col}{auto_str}{c_rst} ({dir_col}{self.play_dir}{c_rst})"),
346
+ (len(f"Speed: {speed_val}/s"), f"{c_bold}Speed: {speed_val}/s{c_rst}")
347
+ ]
348
+
349
+ bottom_items = [
350
+ (len("[P] Play/Pause"), f"[{c_dim}P{c_rst}] Play/Pause"),
351
+ (len("[O] Fwd/Rev"), f"[{c_dim}O{c_rst}] Fwd/Rev"),
352
+ (len("[N] Next"), f"[{c_dim}N{c_rst}] Next"),
353
+ (len("[B] Back"), f"[{c_dim}B{c_rst}] Back"),
354
+ (len("[Z] Speed-"), f"[{c_dim}Z{c_rst}] Speed-"),
355
+ (len("[X] Speed+"), f"[{c_dim}X{c_rst}] Speed+"),
356
+ (len("[G] Re-gen"), f"[{c_yellow}G{c_rst}] Re-gen"),
357
+ (len("[M] Mode"), f"[{c_green}M{c_rst}] Mode"),
358
+ (len("[A] Nums-"), f"[{c_cyan}A{c_rst}] Nums-"),
359
+ (len("[S] Nums+"), f"[{c_cyan}S{c_rst}] Nums+"),
360
+ (len("[D] Disorder-"), f"[{c_magenta}D{c_rst}] Disorder-"),
361
+ (len("[F] Disorder+"), f"[{c_magenta}F{c_rst}] Disorder+"),
362
+ (len("[Q] Quit"), f"[{c_red}Q{c_rst}] Quit")
363
+ ]
364
+
365
+ top_chunks = self.layout_items(top_items, 3, cols)
366
+ bottom_chunks = self.layout_items(bottom_items, 6, cols)
367
+
368
+ occupied_lines = len(top_chunks) + len(bottom_chunks) + 5
369
+ max_lines = max(lines - occupied_lines, 1)
370
+ max_slots = max_lines * 2
371
+
372
+ out = [clear_cmd, "\033[0m"]
373
+
374
+ # --- DRAW TOP BOX ---
375
+ out.append(f"{c_frame}╭{'─' * (cols-2)}╮{c_rst}\033[K\r\n")
376
+ for chunk in top_chunks:
377
+ plain_len = sum(item[0] for item in chunk) + (len(chunk) - 1) * 3
378
+ pad = cols - 2 - plain_len
379
+ pad_l = max(pad // 2, 0)
380
+ pad_r = max(pad - pad_l, 0)
381
+ colored_str = f" {c_dim}|{c_rst} ".join(item[1] for item in chunk)
382
+ out.append(f"{c_frame}│{c_rst}{' ' * pad_l}{colored_str}{' ' * pad_r}{c_frame}│{c_rst}\033[K\r\n")
383
+ out.append(f"{c_frame}╰{'─' * (cols-2)}╯{c_rst}\033[K\r\n")
384
+
385
+ # --- DRAW STACKS ---
386
+ half_cols = max((cols - 5) // 2, 0)
387
+ hdr_a = f"{c_rst}STACK A{c_rst}"
388
+ hdr_b = f"{c_rst}STACK B{c_rst}"
389
+ pad_a_len = max(half_cols - 7, 0)
390
+ out.append(f" {hdr_a}{' ' * pad_a_len} {c_dim}│{c_rst} {hdr_b}\033[K\r\n")
391
+
392
+ disp_sa = self.sample_stack(self.stack_a, max_slots)
393
+ disp_sb = self.sample_stack(self.stack_b, max_slots)
394
+
395
+ disp_sa_len = len(disp_sa)
396
+ disp_sb_len = len(disp_sb)
397
+
398
+ for i in range(max_lines):
399
+ idx1, idx2 = i * 2, i * 2 + 1
400
+
401
+ a1 = disp_sa[idx1] if idx1 < disp_sa_len else -1
402
+ a2 = disp_sa[idx2] if idx2 < disp_sa_len else -1
403
+ b1 = disp_sb[idx1] if idx1 < disp_sb_len else -1
404
+ b2 = disp_sb[idx2] if idx2 < disp_sb_len else -1
405
+
406
+ str_a = self.build_bar(a1, a2, half_cols)
407
+ str_b = self.build_bar(b1, b2, half_cols)
408
+
409
+ out.append(f" {str_a} \033[0m{c_dim}│\033[0m {str_b} \033[0m\033[K\r\n")
410
+
411
+ # --- DRAW BOTTOM BOX ---
412
+ out.append(f"{c_frame}╭{'─' * (cols-2)}╮{c_rst}\033[K\r\n")
413
+ for chunk in bottom_chunks:
414
+ plain_len = sum(item[0] for item in chunk) + (len(chunk) - 1) * 3
415
+ pad = cols - 2 - plain_len
416
+ pad_l = max(pad // 2, 0)
417
+ pad_r = max(pad - pad_l, 0)
418
+ colored_str = f" {c_dim}|{c_rst} ".join(item[1] for item in chunk)
419
+ out.append(f"{c_frame}│{c_rst}{' ' * pad_l}{colored_str}{' ' * pad_r}{c_frame}│{c_rst}\033[K\r\n")
420
+ out.append(f"{c_frame}╰{'─' * (cols-2)}╯{c_rst}\033[K")
421
+
422
+ sys.stdout.write("".join(out))
423
+ sys.stdout.flush()
424
+
425
+ def change_elems(self, direction):
426
+ try:
427
+ curr_idx = self.allowed_sizes.index(self.n_elems)
428
+ except ValueError:
429
+ self.allowed_sizes.append(self.n_elems)
430
+ self.allowed_sizes.sort()
431
+ curr_idx = self.allowed_sizes.index(self.n_elems)
432
+
433
+ new_idx = curr_idx + direction
434
+ if 0 <= new_idx < len(self.allowed_sizes):
435
+ self.n_elems = self.allowed_sizes[new_idx]
436
+ self.generate_data()
437
+
438
+ def change_disorder(self, direction):
439
+ new_val = self.disorder + (direction * 5)
440
+ if 0 <= new_val <= 55:
441
+ self.disorder = new_val
442
+ self.generate_data()
443
+
444
+ def run(self):
445
+ self.generate_data()
446
+
447
+ signal.signal(signal.SIGWINCH, self.handle_resize)
448
+
449
+ def quit_signal(sig, frame):
450
+ sys.exit(0)
451
+
452
+ signal.signal(signal.SIGINT, quit_signal)
453
+ signal.signal(signal.SIGTERM, quit_signal)
454
+
455
+ with TerminalTUI():
456
+ while True:
457
+ if self.auto_play:
458
+ target_ops_sec = self.speeds[self.speed_idx]
459
+ self.accumulator += target_ops_sec
460
+ ops_this_frame = self.accumulator // self.fps
461
+ self.accumulator %= self.fps
462
+
463
+ for _ in range(ops_this_frame):
464
+ if self.play_dir == "FWD":
465
+ if self.op_idx < self.total_ops:
466
+ self.exec_op(self.ops[self.op_idx])
467
+ self.op_idx += 1
468
+ else:
469
+ self.auto_play = False
470
+ self.accumulator = 0
471
+ break
472
+ else:
473
+ if self.op_idx > 0:
474
+ self.op_idx -= 1
475
+ self.exec_inv_op(self.ops[self.op_idx])
476
+ else:
477
+ self.auto_play = False
478
+ self.accumulator = 0
479
+ break
480
+
481
+ self.draw_screen()
482
+
483
+ key = get_key(self.frame_delay)
484
+ if key:
485
+ if key.startswith("\x1b"):
486
+ continue
487
+
488
+ k = key.lower()
489
+ if k == 'p':
490
+ self.auto_play = not self.auto_play
491
+ if not self.auto_play: self.accumulator = 0
492
+ elif k == 'o':
493
+ self.play_dir = "REV" if self.play_dir == "FWD" else "FWD"
494
+ elif k == 'x':
495
+ if self.speed_idx < len(self.speeds) - 1: self.speed_idx += 1
496
+ elif k == 'z':
497
+ if self.speed_idx > 0: self.speed_idx -= 1
498
+ elif k == 'n':
499
+ self.auto_play = False
500
+ self.accumulator = 0
501
+ if self.op_idx < self.total_ops:
502
+ self.exec_op(self.ops[self.op_idx])
503
+ self.op_idx += 1
504
+ elif k == 'b':
505
+ self.auto_play = False
506
+ self.accumulator = 0
507
+ if self.op_idx > 0:
508
+ self.op_idx -= 1
509
+ self.exec_inv_op(self.ops[self.op_idx])
510
+ elif k == 'g':
511
+ self.generate_data()
512
+ elif k == 'm':
513
+ self.flag_idx = (self.flag_idx + 1) % len(self.flags)
514
+ self.generate_data()
515
+ elif k == 'a':
516
+ self.change_elems(-1)
517
+ elif k == 's':
518
+ self.change_elems(1)
519
+ elif k == 'd':
520
+ self.change_disorder(-1)
521
+ elif k == 'f':
522
+ self.change_disorder(1)
523
+ elif k == 'q':
524
+ break
525
+
526
+ # ==========================================
527
+ # MAIN ENTRY
528
+ # ==========================================
529
+ def main():
530
+ target, elements, max_disorder = parse_arguments()
531
+ visualizer = PushSwapVisualizer(target, elements, max_disorder)
532
+ visualizer.run()
533
+
534
+
535
+ if __name__ == "__main__":
536
+ main()
@@ -0,0 +1,240 @@
1
+ Metadata-Version: 2.4
2
+ Name: ft_ps_visu
3
+ Version: 1.0.0
4
+ Summary: A terminal visualizer for the 42 push_swap project with real-time TUI, controlled disorder generation, and interactive playback.
5
+ Author: Italo Almeida
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/italoalmeida0/ft_ps_visu
8
+ Project-URL: Issues, https://github.com/italoalmeida0/ft_ps_visu/issues
9
+ Keywords: 42,push_swap,visualizer,terminal,tui,sorting,algorithm
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.7
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Education :: Testing
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Classifier: Topic :: Terminals
23
+ Requires-Python: >=3.7
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Dynamic: license-file
27
+
28
+ # ft_ps_visu
29
+
30
+ A terminal visualizer for the **42 push_swap** project. It generates controlled random sequences with specific disorder levels, runs your `push_swap` executable, and renders a real-time TUI (Terminal User Interface) with interactive playback controls.
31
+
32
+ ---
33
+ <img src="i1.png" width="500">
34
+ ---
35
+
36
+ ## Features
37
+
38
+ - **Real-time TUI visualization** — watch your `push_swap` algorithm sort stacks live in the terminal.
39
+ - **Controlled disorder generation** — creates sequences with precise inversion percentages.
40
+ - **Four test modes** — adaptive, simple, medium, and complex.
41
+ - **Interactive controls** — play/pause, forward/reverse, step-by-step, speed adjustment, and more.
42
+ - **True-color bars** — gradient-colored bars representing values for easy visual tracking.
43
+ - **Responsive layout** — adapts to terminal resize events.
44
+
45
+ ---
46
+
47
+ ## Requirements
48
+
49
+ - Python 3.7+
50
+ - A compiled `push_swap` executable that accepts a `--<mode>` flag and the numbers to sort
51
+ - A terminal that supports:
52
+ - ANSI escape codes
53
+ - True-color (24-bit RGB) for the best experience
54
+ - Alternate screen buffer (`\033[?1049h` / `\033[?1049l`)
55
+
56
+ > **Note:** On Windows, use Windows Terminal, WSL, or any modern terminal emulator. The classic `cmd.exe` console may not render Unicode block characters or true-color correctly.
57
+
58
+ ---
59
+
60
+ ## Installation
61
+
62
+ > **Tip:** If you run into installation errors, try updating `pip` first:
63
+ > ```bash
64
+ > pip install --upgrade pip
65
+ > # or
66
+ > pip3 install --upgrade pip
67
+ > ```
68
+
69
+ ### Option 1: Install from PyPI (recommended)
70
+
71
+ Using `pip`:
72
+
73
+ ```bash
74
+ pip install ft_ps_visu
75
+ ```
76
+
77
+ Using `pip3`:
78
+
79
+ ```bash
80
+ pip3 install ft_ps_visu
81
+ ```
82
+
83
+ User-local install (no sudo required — `pip`):
84
+
85
+ ```bash
86
+ pip install --user ft_ps_visu
87
+ ```
88
+
89
+ Using `python3 -m pip`:
90
+
91
+ ```bash
92
+ python3 -m pip install ft_ps_visu
93
+ ```
94
+
95
+ > **Note:** When using `--user`, the `ft_ps_visu` binary is installed to a user-local `bin/` directory. Make sure this directory is on your `PATH`, or use the `python3 -m` execution methods shown below.
96
+
97
+ ---
98
+
99
+ ### Option 2: Install from source
100
+
101
+ Clone this repository:
102
+
103
+ ```bash
104
+ git clone https://github.com/italoalmeida0/ft_ps_visu.git
105
+ cd ft_ps_visu
106
+ ```
107
+
108
+ Editable / development mode (`pip`):
109
+
110
+ ```bash
111
+ pip install -e .
112
+ ```
113
+
114
+ Normal install (`pip`):
115
+
116
+ ```bash
117
+ pip install .
118
+ ```
119
+
120
+ ---
121
+
122
+ ### Option 3: Run with `pipx` (isolated, no install required)
123
+
124
+ If you have [`pipx`](https://pypa.github.io/pipx/) installed, you can run the visualizer directly without permanently installing it:
125
+
126
+ ```bash
127
+ pipx run ft_ps_visu ./push_swap
128
+ ```
129
+
130
+ Or install it into an isolated environment:
131
+
132
+ ```bash
133
+ pipx install ft_ps_visu
134
+ ```
135
+
136
+ Then run normally:
137
+
138
+ ```bash
139
+ ft_ps_visu ./push_swap
140
+ ```
141
+
142
+ ---
143
+
144
+ Make sure your `push_swap` binary is compiled and executable:
145
+
146
+ ```bash
147
+ make
148
+ chmod +x push_swap
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Usage
154
+
155
+ ### Basic usage
156
+
157
+ ```bash
158
+ ft_ps_visu ./push_swap
159
+ ```
160
+
161
+ With custom number of elements:
162
+
163
+ ```bash
164
+ ft_ps_visu ./push_swap 100
165
+ ```
166
+
167
+ With custom disorder percentage (0–55):
168
+
169
+ ```bash
170
+ ft_ps_visu ./push_swap 500 30
171
+ ```
172
+
173
+ If `ft_ps_visu` is **not** found on your `PATH`, run via the module:
174
+
175
+ ```bash
176
+ python3 -m ft_ps_visu ./push_swap
177
+ ```
178
+
179
+ Or using the `.cli` submodule directly:
180
+
181
+ ```bash
182
+ python3 -m ft_ps_visu.cli ./push_swap
183
+ ```
184
+
185
+ From the cloned source directory (no install required):
186
+
187
+ ```bash
188
+ python3 ft_ps_visu/cli.py ./push_swap
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Controls
194
+
195
+ | Key | Action |
196
+ |-----|--------|
197
+ | `P` | Play / Pause |
198
+ | `O` | Toggle Forward / Reverse direction |
199
+ | `N` | Next step (forward one operation) |
200
+ | `B` | Back step (reverse one operation) |
201
+ | `X` | Increase speed |
202
+ | `Z` | Decrease speed |
203
+ | `G` | Re-generate data with current settings |
204
+ | `M` | Cycle through modes (adaptive → simple → medium → complex) |
205
+ | `A` | Decrease number of elements |
206
+ | `S` | Increase number of elements |
207
+ | `D` | Decrease disorder percentage |
208
+ | `F` | Increase disorder percentage |
209
+ | `Q` | Quit |
210
+
211
+ ---
212
+
213
+ ## Modes / Flags
214
+
215
+ Your `push_swap` must support the following flags (passed as `--<mode>` before the numbers):
216
+
217
+ | Mode | Disorder range | Description |
218
+ |------------|----------------|------------------------------------------|
219
+ | `simple` | 15.0% – 19.9% | Nearly sorted sequences |
220
+ | `medium` | 20.0% – 49.9% | Moderately shuffled sequences |
221
+ | `complex` | 50.0% – 55.0% | Heavily shuffled sequences |
222
+ | `adaptive` | 15.0% – 55.0% | Random disorder across the full spectrum |
223
+
224
+ > **Note:** If your `push_swap` does **not** implement these flags, the visualizer will still work if your program ignores unknown flags and simply sorts the provided numbers.
225
+
226
+ ---
227
+
228
+ ## How it works
229
+
230
+ 1. **Generate** a random sequence with the desired size and disorder level.
231
+ 2. **Run** your `push_swap` executable with the sequence.
232
+ 3. **Capture** the operations printed to `stdout`.
233
+ 4. **Render** a TUI showing both stacks as colored bars.
234
+ 5. **Animate** the operations at the chosen speed, allowing forward and reverse playback.
235
+
236
+ ---
237
+
238
+ ## License
239
+
240
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ ft_ps_visu/__init__.py
5
+ ft_ps_visu/__main__.py
6
+ ft_ps_visu/cli.py
7
+ ft_ps_visu.egg-info/PKG-INFO
8
+ ft_ps_visu.egg-info/SOURCES.txt
9
+ ft_ps_visu.egg-info/dependency_links.txt
10
+ ft_ps_visu.egg-info/entry_points.txt
11
+ ft_ps_visu.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ft_ps_visu = ft_ps_visu.cli:main
@@ -0,0 +1 @@
1
+ ft_ps_visu
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ft_ps_visu"
7
+ version = "1.0.0"
8
+ description = "A terminal visualizer for the 42 push_swap project with real-time TUI, controlled disorder generation, and interactive playback."
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.7"
12
+ authors = [
13
+ {name = "Italo Almeida"}
14
+ ]
15
+ keywords = ["42", "push_swap", "visualizer", "terminal", "tui", "sorting", "algorithm"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.7",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Topic :: Education :: Testing",
28
+ "Topic :: Software Development :: Testing",
29
+ "Topic :: Terminals",
30
+ ]
31
+
32
+ [project.scripts]
33
+ ft_ps_visu = "ft_ps_visu.cli:main"
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/italoalmeida0/ft_ps_visu"
37
+ Issues = "https://github.com/italoalmeida0/ft_ps_visu/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+