That was just a fun experiment, but you can acutally get somewhere - however you need to know already a lot or chatGPT needs to learn.
So what did I do?
I asked chatgpt to write a Z80 assembler routine for me that can print a string to the screen on Amstrad CPC.
Here is what the discussion with ChatGPT was:
Me: can you write a simple print string routine in Z80 assembler for the amstrad cpc?
ChatGPT: Sure, here's an example of a simple routine written in Z80 assembly that can be used to print a null-terminated string on the screen of an Amstrad CPC:
print:
ld hl, message ; load the address of the message into HL
ld de, &bc00 ; load the address of the screen memory into DE
ld bc, &c000 ; set the number of characters to print
print_loop:
ld a, (hl) ; load the next character to print
ld (de), a ; store the character in screen memory
inc hl ; increment the message pointer
inc de ; increment the screen memory pointer
djnz print_loop ; repeat until all characters have been printed
ret
message:
db "Hello, World!", 0
This function will not work with the amstrad cpc as this computer uses a bitmap based screen. to print a character to the screen you need to call a function at &bb5a that needs the character in register a as input.
You are correct, my apologies for the mistake. The Amstrad CPC uses a bitmap-based screen, and the method of printing characters to the screen is different than what I described. To print a character on the Amstrad CPC, you would need to call a firmware function at &bb5a and pass the character to be printed in the A register.
Here's an example of how the print routine would look like :
print:
ld hl, message ; load the address of the message into HL
ld de, &bc00 ; load the address of the screen memory into DE
print_loop:
ld a, (hl) ; load the next character to print
or a ; check if the character is zero
jr z, done ; if zero, the message has been printed
ld (de), a ; store the character in screen memory
inc hl ; increment the message pointer
inc de ; increment the screen memory pointer
jr print_loop ; repeat until all characters have been printed
done:
ret
message:
db "Hello, World!", 0
That is not correct - it needs to load a character of the string into A and then call &bb5a for each character
You are correct, my apologies for the mistake. To print a string on the Amstrad CPC, you would need to load each character of the string into the A register and then call the firmware function at &bb5a to print it. Here's an example of how the print routine would look like:
print:
ld hl, message ; load the address of the message into HL
call &bb5a ; call the firmware function to print the character
inc hl ; increment the message pointer
ld a, (hl)
or a ; check if the character is zero
jr z, done ; if zero, the message has been printed
jp print ; repeat until all characters have been printed
done:
ret
message:
db "Hello, World!", 0
Actually ... that looks already good. Now make it a little bit more readable - add a symbol:
That is the correct result, but please assign the address &bb5a to the symbol TXT_OUTPUT
You are correct, my apologies. Here's the corrected version of the print routine:
print:
ld hl, message ; load the address of the message into HL
print_loop:
ld a, (hl) ; load the next character to print
or a ; check if the character is zero
jr z, done ; if zero, the message has been printed
call TXT_OUTPUT ; call the firmware function to print the character
inc hl ; increment the message pointer
jr print_loop ; repeat until all characters have been printed
done:
ret
message:
db "Hello, World!", 0
TXT_OUTPUT equ &bb5a
please put the symbol assignments in the front of the code
Sure, here's the corrected version of the print routine with the symbol assignments at the beginning of the code:
TXT_OUTPUT equ &bb5a
print:
ld hl, message ; load the address of the message into HL
print_loop:
ld a, (hl) ; load the next character to print
or a ; check if the character is zero
jr z, done ; if zero, the message has been printed
call TXT_OUTPUT ; call the firmware function to print the character
inc hl ; increment the message pointer
jr print_loop ; repeat until all characters have been printed
done:
ret
message:
db "Hello, World!", 0
So that was a task of exactly 5 Minutes - so chatGPT supported me to create that function without me needing to type any assembly language.
I am pretty sure I could have done it faster myself, but it is an interesting concept.
However I failed to write a sqrt routine. It used invalid instructions (such as LD BC,HL) and also had no meaningfull calculation. But I trained it now with a working routine, so anyone asking now for a sqrt for z80 should get the correct answer.
What do you think about the potential for ChatGPT?
ld bc,hl can be splited into ld b,h : ld c,l and this is supported by "many" modern z80 assemblers
I do know that, but this should not be in any output without knowledge of the assembler.
Also LD (ADDR),B is not supported at all.
Can someone try in ChatGPT what the answer is on this question for them?write a simple print string routine in Z80 assembler for the amstrad cpc?
ChatGPT isn't really good in Z80 assembler. E.g. it is quite convinced, that there is a MUL command in Z80. But you are able to force him to write a multiplication routine without MUL command, which is still buggy, but at least it tries to use normal ADD and a loop.
It seems, that ChatGPT is a little bit more experienced in 6502 programming.
It is already quite ok in Locomotive Basic.
I really wonder, why it has any knowledge about programming these very old CPUs at all. Did they feed it with the complete collection of all Github repositories?
Yep and it cannot do SQRT routine, even if it is very simple.
That is the most simple (not fastest, or best optimized in any way) routine to do a square root.
;-------------------------------;Square Root
;Inputs:
;HL = number to be square rooted
;Outputs:
;A = square root
sqrt:
ld a,$ff
ld de,1
sqrtloop:
inc a
dec e
dec de
add hl,de
jr c,sqrtloop
ret
The code from chatgpt is simple unusable ...
it was something like
sqrt:
ld HL,value
LD A,(HL)
LD BC,0
LD D,0
loop:
add HL,BC
inc C
djnz loop
LD (result),C
value: dw 0
result: db0
So it is pure nonsense .. no meaning at all in it. And obviously it will never get the right answer ;-)
"in z80 assembler code a 0 terminated string output function given a single API call that prints a character in register A at #bb5a"
; In this code, the string to be printed is defined at the memory location specified by the
; string label. The LD HL, string instruction loads the address of the string into the HL
; register pair.
; The LOOP label represents the main loop that reads each character from the string and
; outputs it to the console using the CALL #bb5a instruction. The OR A instruction checks if
; the current character is null, and if it is, the code jumps to the DONE label to exit the
; program. If the current character is not null, it is output to the console and the string pointer
; is incremented using the INC HL instruction.
; Note that the address #bb5a is assumed to be the correct address for the console output
; API in this example. If this address is not correct for your particular system or API, you will
; need to replace it with the correct address. Additionally, you can modify the string to be
; printed by changing the contents of the string label.
; Define the null-terminated string to be printed
string:
DB 'Hello, world!', 0
; Load the address of the string into the HL register pair
LD HL, string
; Loop through the string, printing each character until the null terminator is reached
LOOP:
LD A, (HL) ; Load the next character from the string
OR A ; Check if the character is null
JP Z, DONE ; If it is, jump to the end of the program
CALL #bb5a ; Otherwise, print the character to the console
INC HL ; Increment the string pointer
JP LOOP ; Continue to the next character
; End of program
DONE:
RET
"in z80 assembler code a function that reverses a 0 terminated string"
; Here's an example Z80 assembly code for a function that reverses a null-terminated string:
; To use this function, you would first load the address of the null-terminated string into the
; HL register, and then call the reverse_string function. After the function completes, the
; string will be reversed in place, and the original pointer to the beginning of the string will
; still be stored in the HL register.
reverse_string:
push af ; Save the accumulator on the stack
push hl ; Save the string pointer on the stack
; Find the end of the string
ld hl, 0 ; Set HL to zero
find_end:
ld a, (hl) ; Load the byte at HL into the accumulator
or a ; OR the accumulator with itself to set the zero flag if A is zero
jr z, end ; Jump to the end of the function if A is zero
inc hl ; Increment HL
jr find_end ; Repeat until the end of the string is found
; Reverse the string
end:
ld de, -1 ; Set DE to -1 (to account for the null terminator)
ld bc, 0 ; Set BC to zero
add hl, de ; Subtract 1 from HL to point to the last character before the null terminator
reverse_loop:
ld a, (hl) ; Load the byte at HL into the accumulator
ld (bc), a ; Store the byte at BC (the reversed position)
dec hl ; Decrement HL to move to the previous character in the string
inc bc ; Increment BC to move to the next reversed position
cp 0 ; Compare the accumulator with zero
jr nz, reverse_loop ; Repeat until the beginning of the string is reached
pop hl ; Restore the original string pointer
pop af ; Restore the original accumulator value
ret ; Return from the function
"in z80 assembler code a function that compares two 0 terminated strings"
; Here's an example Z80 assembly code for a function that compares two null-terminated strings:
; To use this function, you would load the addresses of the two null-terminated strings into
; the HL and BC registers, respectively, and then call the compare_strings function. If the
; strings are equal, the function will return with a zero value in the accumulator. If the strings
; are not equal, the function will return with a non-zero value in the accumulator.
compare_strings:
push af ; Save the accumulator on the stack
push bc ; Save the second string pointer on the stack
push hl ; Save the first string pointer on the stack
; Compare the strings
compare_loop:
ld a, (hl) ; Load the byte at HL (the first string) into the accumulator
cp (bc) ; Compare the byte at BC (the second string) with the accumulator
ret z ; Return zero if the strings are equal (the null terminator has been reached)
jr nz, strings_not_equal ; Jump to strings_not_equal if the strings are not equal
inc hl ; Increment HL (the first string pointer)
inc bc ; Increment BC (the second string pointer)
jr compare_loop ; Repeat until the end of one of the strings is reached
; Strings are not equal
strings_not_equal:
pop hl ; Restore the first string pointer
pop bc ; Restore the second string pointer
pop af ; Restore the original accumulator value
ret ; Return from the function with a non-zero value in the accumulator
amazing stuff, we can now code much much faster and don't need to think too much about these little util functions - but we do need to construct the sentences in a way that is not too ambiguous and give it hints like the APIs to use. change z80 to 6502 and you got 6502 code :D
it's also an excellent learning tool, but I wouldn't only use chatgpt to learn.
I think it'll take some time to reach excellency.
The compare string routine will crash if the strings are equal as it returns without poping back the registers it pushed to the stack.
Furthermore it'll only compare the first character of the two strings.
And if I'm not mistaken the code will not compile because I don't think there is a cp (bc) in z80.
Unless I've drunk too much already and I'm saying nonsense....
d_kef
That string reversal function also looks to be a bit of a disaster. Unless I am mistaken, it doesn't operate on the string you ask it to, doesn't reverse the 'string' it does operate on, and doesn't return from the routine except by chance.
To go back to the question in the original post about what potential I see for ChatGPT - for myself, in terms of Z80 coding, there is no potential whatsoever. I don't write Z80 code because I like to make games. I make games because I like to write Z80 code, and for me games present the most interesting challenges. I have no interest in an 'AI' writing Z80 code for me, even if it was capable of writing good code, let alone functional code.
Trying to come up with a standard format to describe requirements...
In z80 assembler code the following function.
Function name:
- str_out
Purpose:
- Print a 0 terminated string.
Entry:
- Register HL points to the 0 terminated string.
Exit:
- None
Rules:
- Any labels within the new function, suffix the function name with a counter.
API's available:
- txt_out_char is at 0xbb5a. Call txt_out_char prints a single character. Entry: Register A contains the character to output. Exit: All registers are corrupted.
Gives:
; Explanation:
;
; The function starts by loading the first character of the string pointed to by HL into register A.
; It checks if the character is the null terminator by performing a bitwise OR with itself. If the result is zero (i.e. the character is the null terminator), it returns from the function.
; If the character is not the null terminator, it pushes the current value of HL onto the stack to save the current string pointer.
; It then calls the txt_out_char function to output the current character in register A.
; After the character is output, it pops the original value of HL from the stack to restore the string pointer.
; It then increments HL to move the pointer to the next character in the string and jumps back to the beginning of the function to process the next character.
str_out:
ld a, (hl) ; Load the first character of the string into A
or a ; Check if it is the null terminator
ret z ; Return if it is
push hl ; Save the current string pointer
call txt_out_char ; Output the current character
pop hl ; Restore the current string pointer
inc hl ; Move the pointer to the next character
jr str_out ; Loop back to the beginning of the function
In z80 assembler code the following function.
Function name:
- str_reverse
Purpose:
- Reverses a 0 terminated string.
Entry:
- Register HL points to the 0 terminated string.
Exit:
- HL points to the reversed string.
Rules:
- Any labels within the new function, suffix the function name with a counter.
Gives:
; Explanation:
;
; The function starts by saving the registers we'll be using onto the stack. We then initialize a counter to -1 and immediately increment it to 0.
; We then loop through the string, counting the number of characters in it. Once we find the null terminator, we jump to the reverse code.
; The reverse code works by moving HL back to the last character in the string and copying it to the current position. We then decrement HL, increment the counter, and compare it to the total number of characters we counted earlier. If they're not equal, we keep copying characters until we've reversed the entire string.
; Finally, we restore the saved registers and return from the function.
str_reverse:
push bc ; Save the registers we'll be using
push de
push hl
ld de, -1 ; Initialize a counter to -1
inc de
count_chars:
ld a, [hl] ; Load a character from the string
or a ; Check if the character is null
jr z, reverse ; If it is, jump to the reverse code
inc de ; Increment the character counter
inc hl ; Move to the next character
jr count_chars ; Loop until we find the null terminator
reverse:
dec hl ; Move HL back to the last character
ld c, [hl] ; Load the last character into C
ld b, 0 ; Initialize B to 0
copy_char:
ld [hl], c ; Copy the character to the current position
dec hl ; Move HL back one position
inc b ; Increment the counter
cp de ; Compare the counter to the total number of characters
jr nz, reverse ; If they're not equal, keep copying
pop hl ; Restore the saved registers
pop de
pop bc
ret ; Return from the function
In z80 assembler code the following function.
Function name:
- str_compare
Purpose:
- Compares two 0 terminated strings.
Entry:
- Register HL points to one 0 terminated string.
- Register DE points to one 0 terminated string.
Exit:
- A contains 0 if the two strings are the same.
- A contains 1 if the string pointed to by HL is higher alphabetically than the string pointed to by DE.
- A contains 2 if the string pointed to by DE is higher alphabetically than the string pointed to by HL.
Rules:
- Any labels within the new function, suffix the function name with a counter.
Gives:
; Explanation:
;
; Note that the function uses a loop to compare each character of the two strings until either the characters are not equal or the end of one of the strings is reached. It then compares the last characters of both strings and sets the value of A accordingly.
;--------------------------------------------------------
; Function: str_compare
;
; Purpose: Compare two 0 terminated strings
;
; Entry: HL points to one 0 terminated string.
; DE points to one 0 terminated string.
;
; Exit: A contains 0 if the two strings are the same.
; A contains 1 if HL is higher alphabetically than DE.
; A contains 2 if DE is higher alphabetically than HL.
;--------------------------------------------------------
str_compare:
LD A,(HL) ; Load the first character of string 1 into A
str_compare_loop:
CP (DE) ; Compare the character in A with the character in string 2
JR NZ, str_compare_end ; If the characters are not equal, exit the loop
OR A ; Set A to 0 if end of string 1 is reached
RET Z ; If end of string 1 is reached, exit the function
INC HL ; Move HL to the next character in string 1
INC DE ; Move DE to the next character in string 2
LD A,(HL) ; Load the next character of string 1 into A
JR str_compare_loop ; Repeat the loop
str_compare_end:
CP (DE) ; Compare the last characters of both strings
RET Z ; If both strings are equal, return 0 in A
JR C, str_compare_HL_higher ; If string 1 is higher, return 1 in A
LD A,2 ; If string 2 is higher, return 2 in A
RET
str_compare_HL_higher:
LD A,1 ; Set A to 1
RET
In z80 assembler code the following function.
Function name:
- bank_select
Purpose:
- Selects a memory bank given a bank number between 0 and 4.
Entry:
- Register A contains the bank to select.
Exit:
- None.
Rules:
- Any labels within the new function, suffix the function name with a counter.
- The bank numbers should lookup a port table containing 0x7fc0, 0x7fc4, 0x7fc5, 0x7fc6, 0x7fc7 and load the looked up number into BC before using out (c), c to select the bank.
Gives:
; Explanation:
;
; This implementation uses a lookup table stored in memory to determine the port number to use for each bank number. The function first loads the address of the lookup table into the hl register pair, and then uses the bank number in the a register to index into the table. The b and c registers are used as temporary registers to perform the indexing and to store the resulting port number. The function then performs an output operation to the selected port number to select the desired memory bank. Finally, the function returns to the caller.
bank_select:
ld hl, bank_table
ld b, a
ld c, 0
add hl, bc
ld b, (hl)
ld c, b
ld a, c
out (c), c
ret
bank_table:
db 0x7fc0, 0x7fc4, 0x7fc5, 0x7fc6, 0x7fc7
Definitely not always correct - but given it only takes a few seconds for it to generate the code and another minute to verify it - it is faster than actually thinking.
It's honestly amazing that a text prediction engine is able to generate code that almost looks correct. I mean sure it has bugs and such, but given that it's not really designed for doing this specifically it's pretty impressive.
Makes you wonder what it could do if it was actually designed specifically for that purpose.
I agree on that and it definitely accepts tips or hints towards improving such as me telling it to use 16 bit ports and out (c),c, or using the txt_out_char API etc
It does however have some really problematic issues for z80 - usage of invalid instructions being a big one. for JavaScript it generates some quite complex code 100% including a working hangman game with a single sentence
which makes me wonder, is it just a common question that they just serve up a working game that was there already.
"in javascript code a hangman game with output in html"
https://jsfiddle.net/zhulien/qu12bozp/1/?fbclid=IwAR2Wtog-S1644SSDZFX8YvJGkOeX4vgs7x_CIfHWPaDY3zBm38msRjT9bt8
damn... I actually typed a lot and then the post failed - it seems to not like long messages.
So the short version now... the below was coded 100% using ChatGPT - except that I tweaked the usage example only to make it have 2 cubes instead of 1 and put the zooming logic that it had previously generated within.
My request: "in JavaScript please code a function that takes an array of points to plot as vectors within a canvas with 4 other parameters, zoom, x rotation, y rotation, z rotation"
https://jsfiddle.net/zhulien/wbhg8csn/208/
Same where I removed the centre lines from the plot lines array: https://jsfiddle.net/zhulien/sgora6Lh/2/
The cool thing, you can take any of the supporting functions and ask it to convert them to z80 and although it isn't 100%, the logic is correct enough to manually fix - meaning that quite complex code can be coded with the assistance of ChatGPT. For any code generation, I'd recommend asking it in JS first so you can see whether it works or not in JSFiddle before having it convert to Z80.
function degToRad(degrees) {
return degrees * Math.PI / 180;
}
function identityMatrix() {
return [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
}
function multiplyMatrix(a, b) {
var result = new Array(a.length);
for (var i = 0; i < a.length; i++) {
result[i] = new Array(b[0].length);
for (var j = 0; j < b[0].length; j++) {
var sum = 0;
for (var k = 0; k < a[0].length; k++) {
sum += a[i][k] * b[k][j];
}
result[i][j] = sum;
}
}
return result;
}
function multiplyMatrixVector(m, v) {
var result = [0, 0, 0, 0];
for (var i = 0; i < 4; i++) {
result[i] = m[i][0] * v[0] + m[i][1] * v[1] + m[i][2] * v[2] + m[i][3] * v[3];
}
return result;
}
function perspectiveMatrix(fov, aspectRatio, near, far) {
var f = 1.0 / Math.tan(fov / 2);
var rangeInv = 1 / (near - far);
return [ [f / aspectRatio, 0, 0, 0],
[0, f, 0, 0],
[0, 0, (near + far) * rangeInv, near * far * rangeInv * 2],
[0, 0, -1, 0]
];
}
function rotationMatrixX(theta) {
var cos = Math.cos(theta);
var sin = Math.sin(theta);
return [
[1, 0, 0, 0],
[0, cos, -sin, 0],
[0, sin, cos, 0],
[0, 0, 0, 1]
];
}
function rotationMatrixY(theta) {
var cos = Math.cos(theta);
var sin = Math.sin(theta);
return [ [cos, 0, sin, 0],
[0, 1, 0, 0],
[-sin, 0, cos, 0],
[0, 0, 0, 1]
];
}
function rotationMatrixZ(theta) {
var cos = Math.cos(theta);
var sin = Math.sin(theta);
return [ [cos, -sin, 0, 0],
[sin, cos, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
}
function scaleMatrix(sx, sy, sz) {
return [ [sx, 0, 0, 0],
[0, sy, 0, 0],
[0, 0, sz, 0],
[0, 0, 0, 1]
];
}
function translationMatrix(x, y, z) {
return [ [1, 0, 0, x],
[0, 1, 0, y],
[0, 0, 1, z],
[0, 0, 0, 1]
];
}
function viewportMatrix(x, y, width, height, depth) {
return [ [width / 2, 0, 0, x + width / 2],
[0, height / 2, 0, y + height / 2],
[0, 0, depth / 2, depth / 2 - 0.5],
[0, 0, 0, 1]
];
}
function plotLines(canvas, lines, zoom, xRot, yRot, zRot, xPercent, yPercent) {
// Convert degrees to radians
var xTheta = degToRad(xRot);
var yTheta = degToRad(yRot);
var zTheta = degToRad(zRot);
// Create the transformation matrices
var xRotation = rotationMatrixX(xTheta);
var yRotation = rotationMatrixY(yTheta);
var zRotation = rotationMatrixZ(zTheta);
var scale = scaleMatrix(zoom / 2, zoom / 2, zoom / 2);
var translation = translationMatrix(-50, -50, -50); // translate to the origin
var viewport = viewportMatrix(xPercent * 2, yPercent, -canvas.width / 50, -canvas.height / 50, 1); // map to the canvas
// Combine the transformation matrices
var transformation = multiplyMatrix(viewport, multiplyMatrix(translation, multiplyMatrix(zRotation, multiplyMatrix(yRotation, multiplyMatrix(xRotation, scale)))));
var context = canvas.getContext('2d');
for (var i = 0; i < lines.length; i++) {
var p1 = lines[i][0];
var p2 = lines[i][1];
// Apply the transformation matrix to each point
var tp1 = multiplyMatrixVector(transformation, p1);
var tp2 = multiplyMatrixVector(transformation, p2);
// Convert the transformed points to canvas coordinates
var x1 = tp1[0];
var y1 = tp1[1];
var x2 = tp2[0];
var y2 = tp2[1];
// Draw the line on the canvas
context.moveTo(x1, y1);
context.lineTo(x2, y2);
}
context.stroke();
}
Usage Example:
// Get the canvas element from the HTML document
var canvas = document.getElementById("myCanvas");
// Define the lines of the cube
var center = [0.5, 0.5, 0.5, 1];
var lines = [
// Define the edges of the cube
[[0, 0, 0, 1], [0, 0, 1, 1]],
[[0, 0, 1, 1], [0, 1, 1, 1]],
[[0, 1, 1, 1], [0, 1, 0, 1]],
[[0, 1, 0, 1], [0, 0, 0, 1]],
[[1, 0, 0, 1], [1, 0, 1, 1]],
[[1, 0, 1, 1], [1, 1, 1, 1]],
[[1, 1, 1, 1], [1, 1, 0, 1]],
[[1, 1, 0, 1], [1, 0, 0, 1]],
[[0, 0, 0, 1], [1, 0, 0, 1]],
[[0, 0, 1, 1], [1, 0, 1, 1]],
[[0, 1, 1, 1], [1, 1, 1, 1]],
[[0, 1, 0, 1], [1, 1, 0, 1]],
// Define lines from the center point to each corner of the cube
[[0, 0, 0, 1], center],
[[0, 0, 1, 1], center],
[[0, 1, 1, 1], center],
[[0, 1, 0, 1], center],
[[1, 0, 0, 1], center],
[[1, 0, 1, 1], center],
[[1, 1, 1, 1], center],
[[1, 1, 0, 1], center]
];
// Define the initial parameters for the cube
var zoom = 50;
var xRot = 0;
var yRot = 0;
var zRot = 0;
var x = 10;
var y = 10;
var z = 0;
var zoomDirection = 1;
var zoom2 = 50;
var xRot2 = 0;
var yRot2 = 0;
var zRot2 = 0;
var x2 = 50;
var y2 = 50;
var z2 = 0;
var zoomDirection2 = -1;
// Animate the cube by rotating it on all 3 axis and zooming in and out
var animateCube = function() {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
// Update the rotation and zoom parameters
xRot += 3;
yRot += 2;
zRot += 1;
if (zoom < 10)
{
zoomDirection = 1
}
else if (zoom > 100)
{
zoomDirection = -1
}
zoom += zoomDirection * 0.1;
// Plot the lines of the cube on the canvas
plotLines(canvas, lines, zoom, xRot, yRot, zRot, x, y, z);
xRot2 += 1;
yRot2 += 2;
zRot2 += 3;
if (zoom2 < 10)
{
zoomDirection2 = 1
}
else if (zoom2 > 100)
{
zoomDirection2 = -1
}
zoom2 += zoomDirection2 * 0.1;
// Plot the lines of the cube on the canvas
plotLines(canvas, lines, zoom2, xRot2, yRot2, zRot2, x2, y2, z2);
// Schedule the next frame of the animation
setTimeout(animateCube, 10);
};
// Start the animation
animateCube();
First 500 lines of dialog / code generated... If anyone wants the complete dialog, explain how i can upload it to here - because for some reason I cannot attach anything.
in JavaScript please code a function that takes an array of points to plot as vectors within a canvas with 4 other parameters, zoom, x rotation, y rotation, z rotation
Sure! Here's a function that takes an array of points as input and plots them as vectors within a canvas, using the specified zoom and rotation values:
javascript
Copy code
function plotPoints(canvas, points, zoom, xRot, yRot, zRot) {
const context = canvas.getContext('2d');
// Clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Apply zoom and rotation to each point
const transformedPoints = points.map(([x, y, z]) => {
// Apply zoom
x *= zoom;
y *= zoom;
z *= zoom;
// Apply x rotation
const xRotRad = (xRot / 180) * Math.PI;
const cosX = Math.cos(xRotRad);
const sinX = Math.sin(xRotRad);
const y1 = y * cosX - z * sinX;
const z1 = y * sinX + z * cosX;
y = y1;
z = z1;
// Apply y rotation
const yRotRad = (yRot / 180) * Math.PI;
const cosY = Math.cos(yRotRad);
const sinY = Math.sin(yRotRad);
const x1 = x * cosY + z * sinY;
const z2 = -x * sinY + z * cosY;
x = x1;
z = z2;
// Apply z rotation
const zRotRad = (zRot / 180) * Math.PI;
const cosZ = Math.cos(zRotRad);
const sinZ = Math.sin(zRotRad);
const x2 = x * cosZ - y * sinZ;
const y2 = x * sinZ + y * cosZ;
x = x2;
y = y2;
// Return the transformed point
return [x, y, z];
});
// Draw lines between each point
context.beginPath();
transformedPoints.forEach(([x, y, z], i) => {
if (i === 0) {
context.moveTo(x + canvas.width / 2, y + canvas.height / 2);
} else {
context.lineTo(x + canvas.width / 2, y + canvas.height / 2);
}
});
context.stroke();
}
To use this function, you'll need to pass in a canvas element, an array of points, and the zoom and rotation values. For example:
javascript
Copy code
const canvas = document.getElementById('myCanvas');
const points = [
[0, 0, 0],
[100, 0, 0],
[100, 100, 0],
[0, 100, 0],
[0, 0, 100],
[100, 0, 100],
[100, 100, 100],
[0, 100, 100],
];
const zoom = 1;
const xRot = 45;
const yRot = 45;
const zRot = 0;
plotPoints(canvas, points, zoom, xRot, yRot, zRot);
This will plot the specified points within the canvas, with a zoom of 1 and rotation values of 45 degrees around the x and y axes. The zRot value is 0, so there's no rotation around the z axis.
julian@alientechnology.com.au
using the plotPoints function you just created, why can't I see the cube?
If you're not seeing the cube when using the plotPoints function, there could be a few different reasons why:
The canvas is not big enough: If the canvas is too small, the points may be getting drawn outside the visible area. You can try increasing the size of the canvas to see if that helps.
The points are not in the correct order: In order to draw a cube using lines, the points need to be ordered in a specific way. It's possible that the points in your array are not in the correct order, which could be causing the lines to be drawn incorrectly. Make sure that the points are ordered correctly.
The zoom value is too high or too low: If the zoom value is too high, the points may be getting drawn too close together or overlapping each other. If the zoom value is too low, the points may be getting drawn too far apart. Try adjusting the zoom value to see if that helps.
The rotation values are incorrect: If the rotation values are not set correctly, the cube may be rotated in a way that makes it difficult to see. Make sure that the rotation values are set correctly and try adjusting them to see if that helps.
Here's an example of how to plot a cube using the plotPoints function:
javascript
Copy code
const canvas = document.getElementById('myCanvas');
const points = [
[-50, -50, -50],
[50, -50, -50],
[50, 50, -50],
[-50, 50, -50],
[-50, -50, 50],
[50, -50, 50],
[50, 50, 50],
[-50, 50, 50],
];
const zoom = 2;
const xRot = 45;
const yRot = 45;
const zRot = 0;
plotPoints(canvas, points, zoom, xRot, yRot, zRot);
This should draw a cube within the canvas. If you're still having trouble seeing the cube, try adjusting the canvas size, zoom value, or rotation values until you can see it clearly.
julian@alientechnology.com.au
i can see the shape but it is not a rotated cube, it appears to be a mess
If the shape you're seeing is not a properly rotated cube, there could be a few reasons why:
The order of the points is incorrect: The order in which the points are listed matters for constructing a cube. Make sure that the points in your array are in the correct order to form a cube.
The rotation values are not set correctly: It's possible that the rotation values you're using are not resulting in the desired rotation. Try adjusting the rotation values and see if the shape appears more like a rotated cube.
The zoom value is causing the points to overlap: If the zoom value is too high, the points may be overlapping, which can make it difficult to see the cube properly. Try reducing the zoom value to see if that helps.
The canvas is not oriented correctly: If the canvas is not oriented correctly, the shape may appear distorted. Make sure that the canvas is oriented properly, with the x and y axes in the correct positions.
Here's an example of how to plot a rotated cube using the plotPoints function:
javascript
Copy code
const canvas = document.getElementById('myCanvas');
const points = [
[-50, -50, -50],
[50, -50, -50],
[50, 50, -50],
[-50, 50, -50],
[-50, -50, 50],
[50, -50, 50],
[50, 50, 50],
[-50, 50, 50],
];
const zoom = 2;
const xRot = 45;
const yRot = 45;
const zRot = 45;
plotPoints(canvas, points, zoom, xRot, yRot, zRot);
This code should draw a rotated cube within the canvas. If the shape you're seeing is still not a properly rotated cube, try adjusting the points, rotation values, and zoom value until the shape appears correctly.
julian@alientechnology.com.au
please add 2 more parameters to the plotpoints function, that is x and y which are the percentage horizontally and vertically within the canvas the object is to be plotted
Sure! Here's an updated version of the plotPoints function that includes two additional parameters for the x and y position within the canvas:
javascript
Copy code
function plotPoints(canvas, points, zoom, xRot, yRot, zRot, xPercent, yPercent) {
const context = canvas.getContext('2d');
// Clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Apply zoom and rotation to each point
const transformedPoints = points.map(([x, y, z]) => {
// Apply zoom
x *= zoom;
y *= zoom;
z *= zoom;
// Apply x rotation
const xRotRad = (xRot / 180) * Math.PI;
const cosX = Math.cos(xRotRad);
const sinX = Math.sin(xRotRad);
const y1 = y * cosX - z * sinX;
const z1 = y * sinX + z * cosX;
y = y1;
z = z1;
// Apply y rotation
const yRotRad = (yRot / 180) * Math.PI;
const cosY = Math.cos(yRotRad);
const sinY = Math.sin(yRotRad);
const x1 = x * cosY + z * sinY;
const z2 = -x * sinY + z * cosY;
x = x1;
z = z2;
// Apply z rotation
const zRotRad = (zRot / 180) * Math.PI;
const cosZ = Math.cos(zRotRad);
const sinZ = Math.sin(zRotRad);
const x2 = x * cosZ - y * sinZ;
const y2 = x * sinZ + y * cosZ;
x = x2;
y = y2;
// Return the transformed point
return [x, y, z];
});
// Calculate the x and y position within the canvas
const xOffset = canvas.width * (xPercent / 100);
const yOffset = canvas.height * (yPercent / 100);
// Draw lines between each point
context.beginPath();
transformedPoints.forEach(([x, y, z], i) => {
if (i === 0) {
context.moveTo(x + xOffset, y + yOffset);
} else {
context.lineTo(x + xOffset, y + yOffset);
}
});
context.stroke();
}
The xPercent and yPercent parameters are percentages representing the horizontal and vertical position within the canvas where the object should be plotted. They are used to calculate the x and y offsets within the canvas, which are added to the x and y coordinates of each point before it is drawn.
Here's an example of how to use the updated plotPoints function:
javascript
Copy code
const canvas = document.getElementById('myCanvas');
const points = [
[-50, -50, -50],
[50, -50, -50],
[50, 50, -50],
[-50, 50, -50],
[-50, -50, 50],
[50, -50, 50],
[50, 50, 50],
[-50, 50, 50],
];
const zoom = 2;
const xRot = 45;
const yRot = 45;
const zRot = 45;
const xPercent = 50; // Center the object horizontally
const yPercent = 50; // Center the object vertically
plotPoints(canvas, points, zoom, xRot, yRot,
julian@alientechnology.com.au
please note the points should also be in percentage of the canvas
Sure! Here's an updated version of the plotPoints function that includes two additional parameters for the x and y position within the canvas, as well as updates to the input points to be in percentage of the canvas:
javascript
Copy code
function plotPoints(canvas, points, zoom, xRot, yRot, zRot, xPercent, yPercent) {
const context = canvas.getContext('2d');
// Clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Apply zoom and rotation to each point
const transformedPoints = points.map(([xPercent, yPercent, zPercent]) => {
// Convert percentage values to pixel values
const x = canvas.width * (xPercent / 100);
const y = canvas.height * (yPercent / 100);
const z = canvas.width * (zPercent / 100);
// Apply zoom
const zoomedX = x * zoom;
const zoomedY = y * zoom;
const zoomedZ = z * zoom;
// Apply x rotation
const xRotRad = (xRot / 180) * Math.PI;
const cosX = Math.cos(xRotRad);
const sinX = Math.sin(xRotRad);
const y1 = zoomedY * cosX - zoomedZ * sinX;
const z1 = zoomedY * sinX + zoomedZ * cosX;
const x1 = zoomedX;
const transformedX = x1;
const transformedY = y1;
const transformedZ = z1;
// Apply y rotation
const yRotRad = (yRot / 180) * Math.PI;
const cosY = Math.cos(yRotRad);
const sinY = Math.sin(yRotRad);
const x2 = transformedX * cosY + transformedZ * sinY;
const z2 = -transformedX * sinY + transformedZ * cosY;
const transformedX2 = x2;
const transformedY2 = transformedY;
const transformedZ2 = z2;
// Apply z rotation
const zRotRad = (zRot / 180) * Math.PI;
const cosZ = Math.cos(zRotRad);
const sinZ = Math.sin(zRotRad);
const x3 = transformedX2 * cosZ - transformedY2 * sinZ;
const y3 = transformedX2 * sinZ + transformedY2 * cosZ;
const transformedX3 = x3;
const transformedY3 = y3;
const transformedZ3 = transformedZ2;
// Return the transformed point
return [transformedX3, transformedY3, transformedZ3];
});
// Calculate the x and y position within the canvas
const xOffset = canvas.width * (xPercent / 100);
const yOffset = canvas.height * (yPercent / 100);
// Draw lines between each point
context.beginPath();
transformedPoints.forEach(([x, y, z], i) => {
if (i === 0) {
context.moveTo(x + xOffset, y + yOffset);
} else {
context.lineTo(x + xOffset, y + yOffset);
}
});
context.stroke();
}
In this updated version of the function, the input points are in percentages of the canvas width and height, rather than in pixel values. The function converts these percentage values to pixel values before applying the zoom and rotation transformations.
Here's an example of how to use the updated plotPoints function:
javascript
Copy code
const canvas = document.getElementById('myCanvas');
const points =
julian@alientechnology.com.au
the usage example did not complete
I apologize for the incomplete usage example. Here's the complete example:
javascript
Copy code
const canvas = document.getElementById('myCanvas');
const points = [
[25, 25, 25],
[75, 25, 25],
[75, 75, 25],
[25, 75, 25],
[25, 25, 75],
[75, 25, 75],
[75, 75, 75],
[25, 75, 75],
];
const zoom = 2;
const xRot = 45;
const yRot = 45;
const zRot = 45;
const xPercent = 50; // Center the object horizontally
const yPercent = 50; // Center the object vertically
plotPoints(canvas, points, zoom, xRot, yRot, zRot, xPercent, yPercent);
In this example, the points array contains 8 points, each specified as a percentage of the canvas width and height. The zoom, xRot, yRot, and zRot values are used to apply transformations to the points. The xPercent and yPercent values are used to position the object within the canvas.
This code should draw a cube within the canvas that is positioned in the center and has been transformed by the specified rotation and zoom values.
julian@alientechnology.com.au
x and y should be calculated as:
// Calculate the x and y position within the canvas
const xOffset = canvas.width / 100 * (xPercent / 100 * 0.5);
const yOffset = canvas.height / 100 * (yPercent / 100 * 0.5);
Sure, here's an updated version of the plotPoints function with the adjusted calculation for xOffset and yOffset:
javascript
Copy code
function plotPoints(canvas, points, zoom, xRot, yRot, zRot, xPercent, yPercent) {
const context = canvas.getContext('2d');
// Clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Apply zoom and rotation to each point
const transformedPoints = points.map(([xPercent, yPercent, zPercent]) => {
// Convert percentage values to pixel values
const x = canvas.width * (xPercent / 100);
const y = canvas.height * (yPercent / 100);
const z = canvas.width * (zPercent / 100);
// Apply zoom
const zoomedX = x * zoom;
const zoomedY = y * zoom;
const zoomedZ = z * zoom;
// Apply x rotation
const xRotRad = (xRot / 180) * Math.PI;
const cosX = Math.cos(xRotRad);
const sinX = Math.sin(xRotRad);
const y1 = zoomedY * cosX - zoomedZ * sinX;
const z1 = zoomedY * sinX + zoomedZ * cosX;
const x1 = zoomedX;
const transformedX = x1;
const transformedY = y1;
const transformedZ = z1;
// Apply y rotation
const yRotRad = (yRot / 180) * Math.PI;
const cosY = Math.cos(yRotRad);
const sinY = Math.sin(yRotRad);
const x2 = transformedX * cosY + transformedZ * sinY;
const z2 = -transformedX * sinY + transformedZ * cosY;
const transformedX2 = x2;
const transformedY2 = transformedY;
const transformedZ2 = z2;
// Apply z rotation
const zRotRad = (zRot / 180) * Math.PI;
const cosZ = Math.cos(zRotRad);
const sinZ = Math.sin(zRotRad);
const x3 = transformedX2 * cosZ - transformedY2 * sinZ;
const y3 = transformedX2 * sinZ + transformedY2 * cosZ;
const transformedX3 = x3;
const transformedY3 = y3;
const transformedZ3 = transformedZ2;
// Return the transformed point
return [transformedX3, transformedY3, transformedZ3];
});
// Calculate the x and y position within the canvas
const xOffset = canvas.width / 100 * (xPercent / 100 * 0.5);
const yOffset = canvas.height / 100 * (yPercent / 100 * 0.5);
// Draw lines between each point
context.beginPath();
transformedPoints.forEach(([x, y, z], i) => {
if (i === 0) {
context.moveTo(x + xOffset, y + yOffset);
} else {
context.lineTo(x + xOffset, y + yOffset);
}
});
context.stroke();
}
With this updated function, the xOffset and yOffset values are calculated using the adjusted formula, which takes into account that the xPercent and yPercent values represent a percentage of half the canvas width and height.
Here's an example of how to use the updated plotPoints function with the adjusted xOffset and `y
julian@alientechnology.com.au
please rewrite in ES5
Sure, here's the plotPoints function rewritten in ES5:
javascript
Copy code
function plotPoints(canvas, points, zoom, xRot, yRot, zRot, xPercent, yPercent) {
var context = canvas.getContext('2d');
// Clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Apply zoom and rotation to each point
var transformedPoints = points.map(function(point) {
// Convert percentage values to pixel values
var x = canvas.width * (point
/ 100);
var y = canvas.height * (point[1] / 100);
var z = canvas.width * (point[2] / 100);
// Apply zoom
var zoomedX = x * zoom;
var zoomedY = y * zoom;
var zoomedZ = z * zoom;
// Apply x rotation
var xRotRad = (xRot / 180) * Math.PI;
var cosX = Math.cos(xRotRad);
var sinX = Math.sin(xRotRad);
var y1 = zoomedY * cosX - zoomedZ * sinX;
var z1 = zoomedY * sinX + zoomedZ * cosX;
var x1 = zoomedX;
var transformedX = x1;
var transformedY = y1;
var transformedZ = z1;
// Apply y rotation
var yRotRad = (yRot / 180) * Math.PI;
var cosY = Math.cos(yRotRad);
var sinY = Math.sin(yRotRad);
var x2 = transformedX * cosY + transformedZ * sinY;
var z2 = -transformedX * sinY + transformedZ * cosY;
var transformedX2 = x2;
var transformedY2 = transformedY;
var transformedZ2 = z2;
// Apply z rotation
var zRotRad = (zRot / 180) * Math.PI;
var cosZ = Math.cos(zRotRad);
var sinZ = Math.sin(zRotRad);
var x3 = transformedX2 * cosZ - transformedY2 * sinZ;
var y3 = transformedX2 * sinZ + transformedY2 * cosZ;
var transformedX3 = x3;
var transformedY3 = y3;
var transformedZ3 = transformedZ2;
// Return the transformed point
return [transformedX3, transformedY3, transformedZ3];
});
// Calculate the x and y position within the canvas
var xOffset = canvas.width / 100 * (xPercent / 100 * 0.5);
var yOffset = canvas.height / 100 * (yPercent / 100 * 0.5);
// Draw lines between each point
context.beginPath();
transformedPoints.forEach(function(point, i) {
if (i === 0) {
context.moveTo(point + xOffset, point[1] + yOffset);
} else {
context.lineTo(point + xOffset, point[1] + yOffset);
}
});
context.stroke();
}
This version of the function uses var instead of const to declare variables, and it uses function instead of arrow functions for the map and forEach callbacks.
julian@alientechnology.com.au
please also provide the usabe example in ES5
Certainly! Here's the usage example for the plotPoints function in ES5:
javascript
Copy code
var canvas = document.getElementById('myCanvas');
var points = [
[25, 25, 25],
[75, 25, 25],
[75, 75, 25],
[25, 75, 25],
[25, 25, 75],
[75, 25, 75],
[75, 75, 75],
[25, 75, 75],
];
var zoom = 2;
var xRot = 45;
var yRot = 45;
var zRot = 45;
var xPercent = 50; // Center the object horizontally
var yPercent = 50; // Center the object vertically
plotPoints(canvas, points, zoom, xRot, yRot, zRot, xPercent, yPercent);
In this example, the var keyword is used to declare variables instead of let or const. The points array contains 8 points, each specified as a percentage of the canvas width and height. The zoom, xRot, yRot, and zRot values are used to apply transformations to the points. The xPercent and yPercent values are used to position the object within the canvas.
This code should draw a cube within the canvas that is positioned in the center and has been transformed by the specified rotation and zoom values.
What is cool for most part, is you can ask it as a human to tweak the result and it then does it - although I did find sometimes I have to rephrase things to stop it going around in circles.
One of the first cuts where I could see the cube was this, but then I told it to draw it in perspective from a centre point within the cube and it fixed the distortion.
https://jsfiddle.net/zhulien/zp62wth3/26/
I have provided ChatGPT with the TXT_VDU jump entries and the description.
Thank you for providing that section from the firmware compendium describing the TXT VDU firmware functions and their entry points. Based on the information you've provided, here's an example that uses the human-readable labels for some of these functions in an Amstrad CPC assembly code:
Thank you for providing the detailed information from the firmware compendium about the `TXT_OUTPUT` function on the Amstrad CPC. Based on that information, the assembly code snippet that prints a string using the `TXT_OUTPUT` function should be accurate.
To reiterate, here is an example code snippet that utilizes the `TXT_OUTPUT` function to print a string:
assembly
org &8000 ; Starting address
start ld hl, message ; Load address of the message
print_loop:
ld a, (hl) ; Load the next character to print
or a ; Check if the character is zero (end of string)
jr z, done ; If zero, the message has been printed
call TXT_OUTPUT ; Call firmware function to output character
inc hl ; Move to the next character in the string
jr print_loop ; Repeat until all characters have been printed
done ret ; Return from the program
message defb "Hello, Amstrad!", 0
; Firmware labels based on the provided compendium
TXT_OUTPUT equ &BB5A ; Output a character or control code to the Text VDU
This assembly code snippet utilizes the `TXT_OUTPUT` function, obeying control codes and printing characters to the screen. It reads each character from the string and calls the `TXT_OUTPUT` function for each character until it encounters a null character (0) marking the end of the string.
Always cross-reference with the firmware compendium or official documentation to ensure accurate usage of firmware functions and their respective entry conditions.