I don't know anything useful about HTML/CSS/JS/React/Node/Express/GraphQL/REST/... but I know how to use LLM to generate code. So i'll make a webapp using LLM only and see how far i can go.
I'll use Jetbrain Webstorm, with the Github Copilot & AI Assistant plugin. Wish me luck ?
Most of the text here will be the result of the AI Assistant, with some comments and explanations from me.
100% of the code will be generated by the AI Assistant.
Selecting a framework
Webstorm have the following project templates:
Angular CLI
Bootstrap
Express
HTML5 Boilerplate
Next.js
Node.js
React
React Native
Vite
Vue.js
I'll ask Bard what i should use.
...
Well, i'll just use HTML5 Boilerplate. It has things, and stuff. Aaaaand, I'm already lost. Should i start with an empty project ? Yes.
What now ?
Ask JetBrains AI Assistant.
To start creating an HTML5 Canvas WebApp, follow these steps:
Create HTML file: First, you need to create an HTML file which will serve as the entry point to your application. Right-click on the project explorer, select New -> HTML file, give it a name, and press Enter.
Add canvas element in HTML file: Add a <canvas> element to your HTML file. The canvas element is a container for graphics, where you can draw graphics via JavaScript.
Create JavaScript file: Create a new JavaScript file where we will add our code for the Canvas. Like before, right-click on the project explorer, select New -> JavaScript file, give it a name, and press Enter.
Draw on canvas: In your JavaScript file, you can obtain a reference to the canvas and then draw on it. Here is some example code that draws a rectangle:
let canvas = document.getElementById('myCanvas'); let ctx = canvas.getContext('2d'); ctx.fillStyle = "#FF0000"; ctx.fillRect(0, 0, 150, 75);
Link JavaScript file in HTML file: Don't forget to include the JavaScript file into the HTML file: <script src="your_filename.js"></script>
Run Your Project: Right click on your HTML file in the project explorer and click "Open in Browser" to see your canvas in action. You can expand on the above steps to include more features for your specific WebApp. You might want to add event listeners for user interactions, or create game logic if you're planning to develop a canvas-based game. Remember to frequently save your work and make sure to commit your changes in your version control system to keep track of your project's progression.
index.html
Well i guess i should have written a better prompt. But ok, i created a default index.html.
The canvas is positioned absolutely, and it spans the full width and height of the page with CSS width and height set to 100%.
The script tag at the end of the body is setting the canvas width and height to match the window's innerWidth and innerHeight. This makes the canvas truly full screen.
Well, it looks like it works
Refactoring
Following the initial instructions, I created an empty main.js file. But first, let's as AI assistant to suggest refactoring. Here is the result:
There is a conflict between AI Assistant and Copilot for auto-completion.
I didn't ask for documentation, therefore i didn't get documentation. But we can create custom AI actions to add documentation to the code.
Done. And it works :
// Fetch the HTML canvas by its Id 'myCanvas'
const canvas = document.getElementById('myCanvas');
// Set the canvas width to match the window's inner width
canvas.width = window.innerWidth;
// Set the canvas height to match the window's inner height
canvas.height = window.innerHeight;
/*
* Reset rule:
* Applies to every element. It resets the margin and padding to 0
* to remove the browser's default styling.
*/
* {
margin: 0;
padding: 0;
}
/*
* #myCanvas rule:
* Applies to the element with id "myCanvas".
* This element is set to an absolute position,
* and having 100% width and height makes it occupy the full viewport.
*/
#myCanvas {
position: absolute;
width: 100%;
height: 100%;
}
Reset rule ?
WTH is a reset rule ? I'll ask for more information.
A CSS reset rule is used to reduce browser inconsistencies in things like default line heights, margins, and font sizes of headings, etc. It involves setting the styles that you want to have as the defaults in every browser, thus setting a level playing field for all of your styles moving forward. In your provided CSS snippet, this line of code:
* {
margin: 0;
padding: 0;
}
It is indeed a CSS Reset rule. It targets all (*) elements and sets their margin and padding properties to 0, removing the default space that browsers apply to these properties for most elements. It's a way to maintain unified styles across different browsers and ensure your design looks consistent. The * selector in CSS is used to select all elements.
Make it square
I asked AI Assistant to make the canvas square. Here is the result:
// Fetch the HTML canvas by its ID 'myCanvas'
const canvas = document.getElementById('myCanvas');
// Get the smaller dimension between the window's inner width and height
const minDimension = Math.min(window.innerWidth, window.innerHeight);
// Set the canvas width and height to match the smaller dimension
canvas.width = minDimension;
canvas.height = minDimension;
But there is a problem, the canvas does not stay square. I'll ask for help.
This happens because the size of the canvas is only set when the page initially loads, not when the window is resized. To keep the canvas square even when the window is resized, you can add an event listener for the window's resize event, and update the canvas dimensions in the event handler. Here's how you can modify your code to do this:
// Fetch the HTML canvas by its ID 'myCanvas'
const canvas = document.getElementById('myCanvas');
// Function to update canvas dimensions
function updateCanvasDimension() {
// Get the smaller dimension between the window's inner width and height
const minDimension = Math.min(window.innerWidth, window.innerHeight);
// Set the canvas width and height to match the smaller dimension
canvas.width = minDimension;
canvas.height = minDimension;
}
// Update canvas dimensions initially
updateCanvasDimension();
// Add an event listener for the resize event
window.addEventListener('resize', updateCanvasDimension);
Now it works. I asked AI to suggest refactoring and find problem. It's mostly about responsive design, but i don't care.
However
it look like a canvas have an internal size and a display size. I'd like to have the internal size to a fixed known value.
// Fetch the HTML canvas by its ID 'myCanvas'
const canvas = document.getElementById('myCanvas');
// Set the canvas width and height to fixed size of 1024
canvas.width = 1024;
canvas.height = 1024;
// Get the context of the canvas
const context = canvas.getContext('2d');
// Set the canvas background to black
context.fillStyle = 'black';
context.fillRect(0, 0, canvas.width, canvas.height);
Refactoring again and setting up the structure of the application
I'll spare the back and forth with AI Assistant, here is where i am so far. I didn't write a single line of code, i asked AI Assistant "refactor" stuff, "rewrite" things, and it did.
app.js
// Constants definition
const CANVAS_SIZE = 1024;
const UPDATE_INTERVAL = 1000;
let canvas;
let context;
// Define setup function
function setup() {
// Fetch the HTML canvas by its id 'myCanvas'
canvas = document.getElementById('myCanvas');
if (!canvas) {
console.error("Unable to find canvas");
return;
}
// Get the context of the canvas
context = canvas.getContext('2d');
if (!context) {
console.error("Unable to get canvas context");
return;
}
setupCanvas(canvas, context);
// Register your keydown callback
window.addEventListener('keydown', handleKeydown);
// Register your mouse event callback
window.addEventListener('click', handleMouseEvent);
console.log('Setup function called');
}
function setupCanvas(canvas, context) {
// Set the canvas width and height to predefined size
canvas.width = CANVAS_SIZE;
canvas.height = CANVAS_SIZE;
// Set the canvas background to black
context.fillStyle = 'black';
context.fillRect(0, 0, canvas.width, canvas.height);
}
// Define update function
function update() {
// Put your update logic here
console.log('Update function called');
}
// Define draw function
function draw() {
// Put your draw logic here
console.log('Draw function called');
// Call requestAnimationFrame again to get smooth animation
requestAnimationFrame(draw);
}
// Replace keypress with keydown callback
function handleKeydown(event) {
console.log('Keydown event: ', event.key);
}
// Define your mouse event callback
function handleMouseEvent(event) {
console.log('Mouse event: ', event.type);
}
// Call setup function
setup();
// Call update function every 'interval' milliseconds
setInterval(update, UPDATE_INTERVAL);
// Call draw function at each animation frame
requestAnimationFrame(draw);
I can't use AI Assistant autocompletion with my french keyboard setup. It's really frustrating, i'll enable Copilot again.
Draw grid
// Define drawGrid function
function drawGrid() {
context.strokeStyle = '#333'; // Dark color but not black
context.lineWidth = 1;
for(let i = 0; i <= GRID_SIZE; i++) {
context.beginPath();
context.moveTo(i * GRID_CELL_SIZE, 0);
context.lineTo(i * GRID_CELL_SIZE, CANVAS_SIZE);
context.stroke();
context.beginPath();
context.moveTo(0, i * GRID_CELL_SIZE);
context.lineTo(CANVAS_SIZE, i * GRID_CELL_SIZE);
context.stroke();
}
}
Fixing CSS
/*
* Reset rule:
* Applies to every element. It resets the margin and padding to 0
* to remove the browser's default styling.
*/
* {
margin: 0;
padding: 0;
box-sizing: border-box; /* Ensures padding and border are included in an element's total width and height. */
}
/*
* Body rule:
* Set the background color to black,
* display it as a flex container which will allow the easy
* alignment of child elements (like canvas). Make it fill
* the full viewport and center align the child elements vertically
* and horizontally.
*/
body {
background-color: black;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
/*
* #myCanvas element styling rule:
* This rule targets an HTML element with an id of myCanvas.
* The width and height of myCanvas are each set to 100vh,
* which means 100% of the viewport's height. The viewport is
* the user's visible area of the web page, so this effectively
* makes the size of myCanvas responsive to the height of the
* viewport.
*/
#myCanvas {
width: 100vh;
height: 100vh;
}
I still didn't write a single line of code or comment. I didn't use auto-completion.
Just a dialog with AI Assistant. I have no idea what i'm doing. It works.
toggle grid
I asked GitHub Copilot for a change
"use strict";
// Constants definition
const CANVAS_SIZE = 1024;
const UPDATE_INTERVAL = 1000 / 60;
const GRID_SIZE = 128;
const GRID_CELL_SIZE = CANVAS_SIZE / GRID_SIZE;
let canvas;
let context;
let isDrawingEnabled = true; // Add this line
// Define setup function
function setup() {
// ... existing code ...
}
function setupCanvas(canvas, context) {
// ... existing code ...
}
// Define drawGrid function
function drawGrid() {
// ... existing code ...
}
// Update draw function
function draw() {
// Clear the current canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Redraw the setup canvas
//setupCanvas(canvas, context);
// Draw the grid only if isDrawingEnabled is true
if (isDrawingEnabled) {
drawGrid();
}
requestAnimationFrame(draw);
}
function update() {
// ... existing code ...
}
function handleKeydown(event) {
// If 'r' key is pressed, toggle isDrawingEnabled
if (event.key === 'r') {
isDrawingEnabled = !isDrawingEnabled;
}
console.log('Keydown event: ', event.key);
}
function handleMouseEvent(event) {
// ... existing code ...
}
setup();
setInterval(update, UPDATE_INTERVAL);
requestAnimationFrame(draw);
And commented to call to setupCanvas in draw function, manually. It's there for a reason, (if the display size change) but i don't care.
A problem with AI Assistant
I'm facing a frustrating problem. AI Assistant doesn't know the context of the code. Unless a specifically provide the code in the prompt, the answer will always be out of context as if i was not in an IDE. Providing the code gets me the right answer, but it's not practical when there is a lot of code in multiple files.
I don't know what to do. I'll ask AI Assistant, of course, but my guess is that i'll have to do some good old google search. (laaaame!)
Define artificial lifeforms
To implement autonomous artificial life that can live, evolve, compete, reproduce, and mutate in the grid, you can follow these steps:
Define a class LifeForm that will represent an individual life form in your world. This class should have properties like health, strength, speed, reproductionRate, mutationRate, etc. It should also have methods for actions like move(), eat(), reproduce(), mutate(), etc.
In your update() function, iterate over all the life forms in your world. For each life form, call its move() method to change its position in the grid, call its eat() method to interact with the environment or other life forms, and call its reproduce() method to potentially create new life forms.
In the reproduce() method, create a new life form with properties based on the parent life form. You can introduce variation by adding a random factor influenced by the mutationRate.
In the eat() method, implement the logic for life forms to compete with each other. For example, a life form could gain health by eating another life form, but only if its strength is higher.
Periodically remove life forms that have health below a certain threshold, representing death.
Here is a basic implementation of the LifeForm class and the updated update() function:
class LifeForm {
constructor(x, y) {
this.x = x;
this.y = y;
this.health = Math.random();
this.strength = Math.random();
this.speed = Math.random();
this.reproductionRate = Math.random();
this.mutationRate = Math.random();
}
move() {
// Implement movement logic here
}
eat() {
// Implement eating logic here
}
reproduce() {
if (Math.random() < this.reproductionRate) {
let child = new LifeForm(this.x, this.y);
child.health = this.health + (Math.random() - 0.5) * this.mutationRate;
child.strength = this.strength + (Math.random() - 0.5) * this.mutationRate;
child.speed = this.speed + (Math.random() - 0.5) * this.mutationRate;
child.reproductionRate = this.reproductionRate + (Math.random() - 0.5) * this.mutationRate;
child.mutationRate = this.mutationRate + (Math.random() - 0.5) * this.mutationRate;
return child;
}
return null;
}
}
let lifeForms = [new LifeForm(0, 0)];
function update() {
for (let i = 0; i < lifeForms.length; i++) {
let lifeForm = lifeForms[i];
lifeForm.move();
lifeForm.eat();
let child = lifeForm.reproduce();
if (child !== null) {
lifeForms.push(child);
}
if (lifeForm.health <= 0) {
lifeForms.splice(i, 1);
i--;
}
}
}
This is a very basic implementation and there are many ways you could expand on this. For example, you could introduce different types of life forms with different properties and behaviors, implement more complex interactions between life forms, or add a graphical representation of the life forms in your grid.
Bugs
The update() function in your code is freezing the application because it's creating an infinite loop. This happens when a new life form is added to the lifeForms array during the iteration over the same array.
Here's the problematic part:
let child = lifeForm.reproduce();
if (child !== null) {
lifeForms.push(child);
}
In this code, if lifeForm.reproduce() returns a new life form, it gets added to the lifeForms array. Since this is happening inside a loop that's iterating over lifeForms, it can create an infinite loop if reproduce() keeps returning new life forms. To fix this, you can create a separate array to hold the new life forms, and then add them to lifeForms after the loop.
Here's how you can do it:
function update() {
let newLifeForms = [];
for (let i = 0; i < lifeForms.length; i++) {
let lifeForm = lifeForms[i];
let child = lifeForm.reproduce();
if (child !== null) {
newLifeForms.push(child);
}
if (lifeForm.health <= 0) {
lifeForms.splice(i, 1);
i--;
}
}
lifeForms = lifeForms.concat(newLifeForms);
console.log('Update function called');
}
In this updated update() function, new life forms are added to the newLifeForms array instead of directly to lifeForms. After the loop, newLifeForms is concatenated to lifeForms, adding all the new life forms at once. This prevents the infinite loop from occurring.
To add a log every time a new child is created, you can add a console.log() statement in the reproduce() method of the LifeForm class, right after a new child is created.