Vibe Coding is a cool way to write programs with the help of AI (artificial intelligence). Imagine having a super-smart assistant by your side while coding! The AI can help you in different ways -- it might create an entire program for you, or just give you ideas and help with specific parts, depending on what you need.
To get started, you simply talk to the AI and explain what you want your program to do. The AI will then create a rough version of the program for you. After that, you can ask the AI questions, make suggestions, or request changes to add new features or fix things until the program is just the way you want it.
For this project, I will begin the AI chat with simple request about drawing a single triangle. As the conversation progresses, the requests will get more complex.
I am using a 2D graphics program to teach JavaScript. It contains a function called: line(x1,y1,x2,y2). The 2D environment has the origin point in the upper left corner and positive values on the y-axis are descending on the computer screen. The width of the draw area is about 400 pixels and the height is about 200 pixels. Draw a triangle with equal sides of length 40. The bottom side of the triangle should be toward the lower end of the screen (i.e., a higher value of y).
Here's a clean, classroom-friendly way to draw an equilateral triangle with side length 40 in your 2D environment.
Because your coordinate system has:
Origin at the upper-left
Positive y going downward
Drawing area about 400x by 200y pixels
...it's easiest to place the bottom side somewhere near the lower portion of the screen, then compute the top vertex using the geometry of an equilateral triangle.
Geometry Setup For an equilateral triangle of side length:
Bottom-left point: (x1, y1)
Bottom-right point: (x2,y1) where x2 = x1 + 40
Height of the triangle: h = sqrt(3)/2 * 40
Top vertex: x3=x1+20, y3=y1-h
Because positive y goes downward, the top vertex has a smaller y value.
Example Code (ready to run)
This places the bottom side at y = 150, comfortably above the bottom of a 200-pixel canvas.
Now create a slightly more complex algorithm that divides each side into two equal line segments. So, instead of drawing one line for each side, you will draw two lines--a left half and a right half.
Here's a clean, modular way to upgrade your triangle-drawing algorithm so that each side is drawn as two equal line segments instead of one. This is a great next step for students because it reinforces midpoint calculations and vector thinking without overwhelming them.
Step 1 -- Start with the same triangle geometry
We'll reuse the equilateral triangle setup:
Bottom-left point: (x1, y1)
Bottom-right point: (x2,y1) where x2 = x1 + 40
Height of the triangle: h = sqrt(3)/2 * 40
Top vertex: x3=x1+20, y3=y1-h
Step 2 -- Compute midpoints for each side For any two points (xa,ya) and (xb,yb), the midpoint is: ((xa+xb)/2, (ya+yb)/2)
We'll compute:
Midpoint of bottom side
Midpoint of left side
Midpoint of right side
Then draw two lines per side.
Full JavaScript Example This places the triangle near the lower part of the screen (y = 150), just like before.
In this particular graphics program, all global variables need to be declared as "var", not "const". I know this isn't the best practice. However, with the way that the graphics API works, this is necessary. Can you re-write it using "var"?
Since your graphics environment treats global scope differently, switching from to is the right move here. Below is the same midpoint-based triangle algorithm, rewritten so all global variables use "var".
Now draw a slightly larger triangle around this one. The larger triangle needs to have the total length of each side as 60 pixels. As for the line segments, this larger triangle should have 3 segments instead of 2. So, it might be better to create a function that performs this line drawing using the segments based on the depth of the triangle. The number of segments will always be the depth of the triangle plus 1. The smallest triangle would be 1+1 and the next larger triangle will be 2+1.
Let's build this up in a way that stays clean, scalable, and very teachable for your students.
You now want:
A small equilateral triangle (side = 40) drawn with 2 segments per side
A larger equilateral triangle (side = 60) drawn with 3 segments per side
A general function that draws any triangle side using segments = depth + 1
This is a perfect moment to introduce a reusable helper that:
Takes two endpoints
Divides the line into equal sub-segments
Draws each segment with line()
Then we plug that into a triangle-drawing function. Below is a clean, fully var-based version that fits your environment.
Step 1 -- A function to draw a subdivided line
This uses linear interpolation to compute evenly spaced points along the line.
Step 2 -- A function to draw an equilateral triangle with a given side length and depth
Step 3 -- Draw the two triangle
You can adjust the positions so the larger triangle surrounds the smaller one more symmetrically. For example:
Small triangle bottom at y = 150
Large triangle bottom at y = 165
Or shift x-values slightly to center them.
After observing the result, I feel that I need to adjust the algorithm slightly.
// Draw a line subdivided into (depth + 1) equal segments
function drawSegmentedLine(x1, y1, x2, y2, depth) {
var segments = depth * 2;
for (var i = 0; i < segments; i++) {
var t1 = i / segments;
var t2 = (i + 1) / segments;
var sx1 = x1 + (x2 - x1) * t1;
var sy1 = y1 + (y2 - y1) * t1;
var sx2 = x1 + (x2 - x1) * t2;
var sy2 = y1 + (y2 - y1) * t2;
line(sx1, sy1, sx2, sy2);
}
}
function drawTriangle(x1, y1, side, depth) {
// Bottom-left and bottom-right
var x2 = x1 + side;
var y2 = y1;
// Height of equilateral triangle
var h = (Math.sqrt(3) / 2) * side;
// Top vertex
var x3 = x1 + side / 2;
var y3 = y1 - h;
// Draw each side using segmented lines
drawSegmentedLine(x1, y1, x2, y2, depth); // bottom
drawSegmentedLine(x1, y1, x3, y3, depth); // left
drawSegmentedLine(x2, y2, x3, y3, depth); // right
}
// Small triangle: side = 40, depth = 1 -> 2 segments per side
drawTriangle(180, 150, 40, 1);
// Larger triangle: side = 60, depth = 2 -> 4 segments per side
drawTriangle(160, 162, 80, 2);
// Larger triangle: side = 80, depth = 3 -> 6 segments per side
drawTriangle(140, 174, 120, 3);
// Larger triangle: side = 100, depth = 4 -> 8 segments per side
drawTriangle(120, 186, 160, 4);
The size of each subsequent triangle increases by 40 pixels. So, we could say the size equals the depth*10. Also the X-coord is 200 - depth * 20 and the Y-coord is 138 + depth * 12
You've got a great pattern emerging here -- you're essentially building a stacked family of equilateral triangles, each one:
Larger than the previous
Positioned lower and more centered
With more line subdivisions
Your observation about the pattern is exactly right:
Side length grows by 40 each time
Depth grows by 1 each time
Segments = depth * 2
Position shifts in a predictable way
And your new formulas:
side = depth * 40
x = 200 - depth * 20
y = 138 + depth * 12
This will give you a beautifully scalable system. Let's rewrite your code so the triangle series is generated automatically from these formulas.
Updated Algorithm With Your New Rules
What This Version Achieves
Perfectly centered, nested triangles Each triangle grows outward by 40 pixels per side, and your x/y formulas keep them visually aligned. Smooth segmentation Depth 1 -> 2 segments Depth 2 -> 4 segments Depth 3 -> 6 segments Depth 4 -> 8 segments Fully scalable You can now draw 10, 20, or 100 triangles by simply increasing the loop.
If you want, we can now: * Add color gradients * Animate the triangles growing * Add rotation * Turn this into a recursive fractal generator
Now I would like to turn this into a very simple maze. Let's assign each side drawn a letter such that the bottom side will be "A" and the left side will be "B" and the right side will be "C". Now, let's select a random line segment from the "A" side and choose not to draw that one segment. Then for the next level of triangle, lets select a random line segment from the "B" side and choose not to draw that one segment. Then for the next level of triangle, lets select a random line segment from the "C" side and do the same. I think a global variable should help keep track of the side that is going to have the missing segment. Then just increment that global variable each time through the loop. Probably, you could include it in the for() loop initialization and increment. For example: for(var depth = 1, side = 1; depth <= 4; depth++, side++) . Then pass the "side" to the drawTriangle() function and use it in a Boolean expression when passing arguments to the drawSegmentedLine() function. Something like: drawSegmentedLine(x1, y1, x2, y2, depth, side%3==1); If the drawSegmentedLine() function receives a "true" as the last parameter, then calculate a random index for the line segment to be skipped and then add an if() condition just before the actual line(sx1, sy1, sx2, sy3) function call.
You're building something genuinely fun here -- a geometric maze generator that grows outward in layers, and each layer has exactly one "missing" segment on a specific side (A, B, or C), rotating in sequence. This is a perfect use case for a global counter and a clean Boolean flag passed into your segmented-line function. Let's build this in a way that's: * Predictable (A -> B -> C -> A -> etc.) * Randomized (which segment is missing) * Scalable (any number of triangle layers) * Compatible with your -only environment Below is a polished version of the algorithm you described.
Step 1 -- Update drawSegmentedLine() to support skipping one random segment We add a parameter skipOne, which tells the function whether this side should have a missing segment.
Step 2 -- Update to accept the "side selector"
We pass in a number that determines which side gets the missing segment: * 1 -> A the bottom * 2 -> B the left * 0 -> C the right because side % 3 cycles as 1, 2, 0, 1, 2, 0 etc.
Step 3 -- Loop through triangle layers
Using your formulas: * side = depth * 40 * x = 200 - depth * 20 * y = 138 + depth * 12 * sideSelector = depth % 3 (which cycles A -> B -> C)
What this produces * Triangle 1 (depth=1) * Side A (bottom) gets a missing segment * Triangle 2 (depth=2) * Side B (left) gets a missing segment * Triangle 3 (depth=3) * Side C (right) gets a missing segment * Triangle 4 (depth=4) * Back to side A Each missing segment is chosen randomly, so the maze changes every run.