about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xawk/forth/f.awk593
-rw-r--r--awk/forth/test.forth157
-rw-r--r--html/isometric-bounce/index.html (renamed from html/voxels/index.html)6
-rw-r--r--html/isometric-bounce/js/game.js335
-rw-r--r--html/voxels/js/game.js345
5 files changed, 1088 insertions, 348 deletions
diff --git a/awk/forth/f.awk b/awk/forth/f.awk
new file mode 100755
index 0000000..23bc9f0
--- /dev/null
+++ b/awk/forth/f.awk
@@ -0,0 +1,593 @@
+#!/usr/bin/awk -f
+
+# Forth interpreter in AWK
+
+BEGIN {
+    print "Welcome to the AWK Forth Interpreter!"
+    print "Type your commands below. Use 'bye' to quit."
+    # Initialize variables
+    top = -1  # Initialize stack pointer
+    
+    # Initialize the dictionary with basic words
+    words["+"] = "+"
+    words["-"] = "-"
+    words["*"] = "*"
+    words["/"] = "/"
+    words["dup"] = "dup"
+    words["over"] = "over"
+    words["swap"] = "swap"
+    words["."] = "."
+    words["bye"] = "bye"
+    words["rot"] = "rot"
+    words["drop"] = "drop"
+    words["nip"] = "nip"
+    words["tuck"] = "tuck"
+    words["roll"] = "roll"
+    words["pick"] = "pick"
+    words["negate"] = "negate"
+    words["abs"] = "abs"
+    words["max"] = "max"
+    words["min"] = "min"
+    words["mod"] = "mod"
+    words["="] = "="
+    words["see"] = "see"
+    words["if"] = "if"
+    words["then"] = "then"
+    words["else"] = "else"
+    words[">"] = ">"
+
+    # Add descriptions for words
+    desc["+"] = "( n1 n2 -- sum ) Add top two numbers"
+    desc["-"] = "( n1 n2 -- diff ) Subtract top number from second"
+    desc["*"] = "( n1 n2 -- prod ) Multiply top two numbers"
+    desc["/"] = "( n1 n2 -- quot ) Divide second by top"
+    desc["dup"] = "( n -- n n ) Duplicate top of stack"
+    desc["over"] = "( n1 n2 -- n1 n2 n1 ) Copy second item to top"
+    desc["swap"] = "( n1 n2 -- n2 n1 ) Swap top two items"
+    desc["rot"] = "( n1 n2 n3 -- n2 n3 n1 ) Rotate top three items"
+    desc["drop"] = "( n -- ) Discard top item"
+    desc["nip"] = "( n1 n2 -- n2 ) Remove second item"
+    desc["tuck"] = "( n1 n2 -- n2 n1 n2 ) Copy top item below second"
+    desc["roll"] = "( nk ... n1 n0 k -- nk-1 ... n1 n0 nk ) Move kth item to top"
+    desc["pick"] = "( nk ... n1 n0 k -- nk ... n1 n0 nk ) Copy kth item to top"
+    desc["negate"] = "( n -- -n ) Negate number"
+    desc["abs"] = "( n -- |n| ) Absolute value"
+    desc["max"] = "( n1 n2 -- max ) Maximum of top two numbers"
+    desc["min"] = "( n1 n2 -- min ) Minimum of top two numbers"
+    desc["mod"] = "( n1 n2 -- rem ) Remainder of n1/n2"
+    desc["="] = "( n1 n2 -- flag ) Test if equal, leaves 1 if true, 0 if false"
+    desc["if"] = "( flag -- ) Begin conditional execution"
+    desc["then"] = "( -- ) End conditional execution"
+    desc["else"] = "( -- ) Execute if previous condition was false"
+    desc[">"] = "( n1 n2 -- flag ) Returns true if n1 is greater than n2"
+
+    # Add variable-related words to dictionary
+    words["variable"] = "variable"
+    words["!"] = "!"
+    words["@"] = "@"
+    words["?"] = "?"
+
+    # Add descriptions for the new words
+    desc["variable"] = "( -- addr ) Create a variable"
+    desc["!"] = "( n addr -- ) Store n at addr"
+    desc["@"] = "( addr -- n ) Fetch contents of addr"
+    desc["?"] = "( addr -- ) Print contents of addr"
+
+    # Initialize test-related words
+    words["test"] = "test"
+    words["testing"] = "testing"
+    desc["test"] = "( n1 n2 -- ) Compare values and report test result"
+    desc["testing"] = "( -- ) Print test description"
+
+    # Initialize condition stack
+    cond_top = -1
+
+    # Mark these as compile-only words
+    compile_only["if"] = 1
+    compile_only["then"] = 1
+    compile_only["else"] = 1
+
+    # Add dot-quote to dictionary
+    words[".\""] = ".\""
+    desc[".\""] = "( -- ) Print following string up to closing quote"
+
+    # Handle file input
+    if (ARGC > 1) {
+        print "Loading file:", ARGV[1]
+        while ((getline < ARGV[1]) > 0) {
+            if ($0 ~ /^[[:space:]]*$/) continue  # Skip empty lines
+            print "> " $0
+            process_line()
+        }
+        close(ARGV[1])
+        exit
+    }
+
+    # Add to dictionary initialization
+    words["<"] = "<"
+    words[">"] = ">"
+    
+    # Add descriptions
+    desc["<"] = "( n1 n2 -- flag ) Returns true if n1 is less than n2"
+    desc[">"] = "( n1 n2 -- flag ) Returns true if n1 is greater than n2"
+
+    # Add handlers
+    handlers["+" ] = "add"
+    handlers["-" ] = "subtract"
+    handlers["*" ] = "multiply"
+    handlers["/" ] = "divide"
+    handlers["dup"] = "dup"
+    handlers["over"] = "over"
+    handlers["swap"] = "swap"
+    handlers["."] = "print_top"
+    handlers["<"] = "less_than"
+    handlers[">"] = "greater_than"
+
+    # Add variable handler
+    handlers["variable"] = "create_variable"
+    handlers["!"] = "store"
+    handlers["@"] = "fetch"
+    handlers["?"] = "print_var"
+}
+
+# Add these helper functions near the top
+function check_stack(min_items, error_msg) {
+    if (top < min_items - 1) {
+        print error_msg ? error_msg : "Error: Not enough values on stack"
+        return 0
+    }
+    return 1
+}
+
+function check_underflow() {
+    return check_stack(1, "Error: Stack underflow")
+}
+
+# Function to define a new word
+function define_word(name, definition) {
+    # Store the raw definition
+    raw_definitions[name] = definition
+    print "DEBUG: Raw definition for " name ": " definition
+    
+    # Process the definition to handle conditionals
+    processed = process_definition(definition)
+    print "DEBUG: Processed definition for " name ": " processed
+    words[name] = processed
+}
+
+function process_definition(definition) {
+    # Normalize whitespace in definition
+    gsub(/^[[:space:]]+/, "", definition)
+    gsub(/[[:space:]]+$/, "", definition)
+    gsub(/[[:space:]]+/, " ", definition)
+    
+    # Split definition into words
+    split(definition, def_words, " ")
+    processed = ""
+    in_string = 0
+    string_content = ""
+    
+    for (i = 1; i <= length(def_words); i++) {
+        word = def_words[i]
+        
+        if (word == ".\"") {
+            # Start collecting string
+            in_string = 1
+            string_content = ""
+            continue
+        }
+        
+        if (in_string) {
+            # Check if this word contains the closing quote
+            if (word ~ /"$/) {
+                # End of string found
+                string_content = string_content (string_content == "" ? "" : " ") substr(word, 1, length(word)-1)
+                processed = processed " <string>" string_content "</string>"
+                in_string = 0
+            } else {
+                # Add to string content
+                string_content = string_content (string_content == "" ? "" : " ") word
+            }
+            continue
+        }
+        
+        # Handle non-string words
+        if (word == "if") {
+            processed = processed " <if>"
+        } else if (word == "else") {
+            processed = processed " <else>"
+        } else if (word == "then") {
+            processed = processed " <then>"
+        } else {
+            processed = processed " " word
+        }
+    }
+    
+    return processed
+}
+
+# Stack to hold values
+function push(value) {
+    stack[++top] = value
+}
+
+function pop() {
+    if (top < 0) {
+        print "Error: Stack underflow"
+        return 0
+    }
+    return stack[top--]
+}
+
+function binary_op(operation) {
+    if (!check_stack(2)) return
+    second = pop()
+    first = pop()
+    if (operation == "+") push(first + second)
+    else if (operation == "-") push(first - second)
+    else if (operation == "*") push(first * second)
+    else if (operation == "/") {
+        if (second == 0) {
+            print "Error: Division by zero"
+            push(first)
+            push(second)
+            return
+        }
+        push(first / second)
+    }
+    else if (operation == "mod") push(first % second)
+    else if (operation == "=") push(first == second ? 1 : 0)
+    else if (operation == "<") push(first < second ? 1 : 0)
+    else if (operation == ">") push(first > second ? 1 : 0)
+}
+
+# Then simplify the operation functions:
+function add() { binary_op("+") }
+function subtract() { binary_op("-") }
+function multiply() { binary_op("*") }
+function divide() { binary_op("/") }
+function mod() { binary_op("mod") }
+function equals() { binary_op("=") }
+function less_than() { binary_op("<") }
+function greater_than() { binary_op(">") }
+
+# Function to duplicate the top value
+function dup() {
+    if (!check_stack(1)) return
+    push(stack[top])
+}
+
+# Function to copy the second value to the top
+function over() {
+    if (!check_stack(2)) return
+    push(stack[top - 1])
+}
+
+# Function to swap the top two values
+function swap() {
+    if (!check_stack(2)) return
+    temp = pop()
+    second = pop()
+    push(temp)
+    push(second)
+}
+
+function print_top() {
+    if (!check_stack(1)) return
+    print stack[top]
+}
+
+# Function to list all available words
+function list_words() {
+    print "Available words:"
+    for (word in words) {
+        print word
+    }
+}
+
+function rot() {
+    if (!check_stack(3)) return
+    third = pop()
+    second = pop()
+    first = pop()
+    push(second)
+    push(third)
+    push(first)
+}
+
+function drop() {
+    if (!check_stack(1)) return
+    top--
+}
+
+function nip() {
+    if (!check_stack(2)) return
+    temp = stack[top]
+    drop()
+    drop()
+    push(temp)
+}
+
+function tuck() {
+    if (!check_stack(2)) return
+    temp = pop()
+    second = pop()
+    push(temp)
+    push(second)
+    push(temp)
+}
+
+function roll() {
+    if (!check_stack(1)) return
+    n = int(pop())
+    if (!check_stack(n)) return
+    if (n <= 0) return
+
+    temp = stack[top - n + 1]
+    for (i = top - n + 1; i < top; i++) {
+        stack[i] = stack[i + 1]
+    }
+    stack[top] = temp
+}
+
+function pick() {
+    if (!check_stack(1)) return
+    n = int(pop())
+    if (!check_stack(n)) return
+    if (n < 0) return
+    push(stack[top - n])
+}
+
+function negate() {
+    if (!check_stack(1)) return
+    push(-pop())
+}
+
+function abs() {
+    if (!check_stack(1)) return
+    n = pop()
+    push(n < 0 ? -n : n)
+}
+
+function max() {
+    if (!check_stack(2)) return
+    b = pop()
+    a = pop()
+    push(a > b ? a : b)
+}
+
+function min() {
+    if (!check_stack(2)) return
+    b = pop()
+    a = pop()
+    push(a < b ? a : b)
+}
+
+function see(word) {
+    if (word in desc) {
+        print desc[word]
+    } else if (word in words) {
+        print ": " word " " words[word] " ;"
+    } else {
+        print "Word '" word "' not found"
+    }
+}
+
+# Add these new functions for variable handling:
+
+# Function to create a new variable
+function create_variable(name) {
+    if (name == "") {
+        print "Error: Variable name required"
+        return
+    }
+    variables[name] = 0  # Initialize variable to 0
+    # No need to push the variable name onto the stack
+}
+
+# Function to store a value in a variable
+function check_variable(addr, error_msg) {
+    if (!(addr in variables)) {
+        print error_msg ? error_msg : "Error: Invalid variable '" addr "'"
+        return 0
+    }
+    return 1
+}
+
+function store() {
+    if (!check_stack(2)) return
+    addr = pop()
+    if (!check_variable(addr)) return
+    variables[addr] = pop()
+}
+
+# Function to fetch a value from a variable
+function fetch() {
+    if (!check_stack(1)) return
+    addr = pop()
+    if (!check_variable(addr)) return
+    push(variables[addr])
+}
+
+# Function to print a variable's value
+function print_var() {
+    if (top < 0) {
+        print "Error: Stack underflow"
+        return
+    }
+    addr = pop()
+    if (!(addr in variables)) {
+        print "Error: Invalid variable '" addr "'"
+        return
+    }
+    print variables[addr]
+}
+
+# Add these test-related functions:
+function run_test(expected) {
+    if (top < 0) {
+        print "❌ FAIL: Stack is empty"
+        return
+    }
+    actual = pop()
+    if (actual == expected) {
+        print "✓ PASS"
+    } else {
+        print "❌ FAIL: Expected", expected, "but got", actual
+    }
+}
+
+function print_test_description() {
+    if (NF < 2) {
+        print "Error: testing requires a description"
+        return
+    }
+    # Collect all words after 'testing' as the description
+    test_desc = ""
+    for (i = 2; i <= NF; i++) {
+        test_desc = test_desc " " $i
+    }
+    print "\nTesting:" test_desc
+}
+
+# Add these new functions for conditional handling:
+
+# Function to handle if statement
+function handle_if() {
+    if (top < 0) {
+        print "Error: Stack underflow"
+        return
+    }
+    # Save condition without consuming it
+    cond_stack[++cond_top] = (stack[top] != 0)
+}
+
+# Function to handle then statement
+function handle_then() {
+    if (cond_top < 0) {
+        print "Error: Unmatched then"
+        return
+    }
+    cond_top--  # Pop condition
+}
+
+# Function to handle else statement
+function handle_else() {
+    if (cond_top < 0) {
+        print "Error: Unmatched else"
+        return
+    }
+    cond_stack[cond_top] = !cond_stack[cond_top]
+}
+
+# Add this function
+function execute_word(word) {
+    if (word in handlers) {
+        if (handlers[word] == "add") add()
+        else if (handlers[word] == "subtract") subtract()
+        else if (handlers[word] == "multiply") multiply()
+        else if (handlers[word] == "divide") divide()
+        else if (handlers[word] == "dup") dup()
+        else if (handlers[word] == "over") over()
+        else if (handlers[word] == "swap") swap()
+        else if (handlers[word] == "print_top") print_top()
+        else if (handlers[word] == "create_variable") {
+            # Special handling for variable creation
+            if (i + 1 <= NF) {
+                create_variable($(++i))
+            } else {
+                print "Error: variable requires a name"
+            }
+        }
+        else if (handlers[word] == "store") store()
+        else if (handlers[word] == "fetch") fetch()
+        else if (handlers[word] == "print_var") print_var()
+        else {
+            print "Error: Handler '" handlers[word] "' not implemented"
+            return 0
+        }
+        return 1
+    }
+    return 0
+}
+
+# Move the main command processing into a function
+function process_line() {
+    # Process input only if it's not empty
+    if (NF > 0) {
+        # Remove comments and normalize whitespace
+        gsub(/\(.*\)/, "")  # Remove everything from ( to )
+        gsub(/^[[:space:]]+/, "")  # Remove leading whitespace
+        gsub(/[[:space:]]+$/, "")  # Remove trailing whitespace
+        gsub(/[[:space:]]+/, " ")  # Normalize internal whitespace
+
+        # Special handling for testing command
+        if ($1 == "testing") {
+            # Collect the description string
+            if ($2 ~ /^".*"$/) {  # Check if the second token is a quoted string
+                test_desc = substr($2, 2, length($2) - 2)  # Remove quotes
+                print "\nTesting: " test_desc
+            } else {
+                print "Error: testing requires a description in quotes"
+            }
+            return
+        }
+
+        # Check for word definitions
+        if ($1 == ":") {
+            word_name = $2
+            definition = ""
+            in_definition = 1
+            # Remove : and word name from the input
+            for (i = 3; i <= NF; i++) {
+                definition = definition " " $i
+            }
+            # If we don't find a semicolon, keep reading lines
+            while (definition !~ /;$/) {  # Changed to match semicolon at end
+                if ((getline) <= 0) break
+                gsub(/\(.*\)/, "")  # Remove comments
+                gsub(/^[[:space:]]+/, "")
+                gsub(/[[:space:]]+$/, "")
+                definition = definition " " $0
+            }
+            # Remove the semicolon
+            sub(/[[:space:]]*;[[:space:]]*$/, "", definition)
+            
+            if (definition != "") {
+                define_word(word_name, definition)
+                print "Defined new word: " word_name
+            }
+            in_definition = 0
+            return
+        }
+
+        # Process remaining input
+        for (i = 1; i <= NF; i++) {
+            if ($i ~ /^-?[0-9]+$/) {
+                push($i)
+            } else if ($i in variables) {  # Check if it's a variable
+                push(variables[$i])  # Push the value of the variable onto the stack
+            } else if ($i in words) {  # Check if it's a defined word
+                if (!in_definition && compile_only[$i]) {
+                    print "Error: '" $i "' is compile-only"
+                    return
+                }
+                if (!execute_word($i)) {
+                    print "Error: Unknown command '" $i "'"
+                }
+            } else if ($i != "") {  # Ignore empty tokens
+                print "Error: Unknown command '" $i "'"
+            }
+        }
+    }
+}
+
+# Main loop to read commands (for interactive mode)
+{
+    process_line()
+}
+
+# Add function to handle dot-quote
+function print_string(str) {
+    printf "%s", str
+}
diff --git a/awk/forth/test.forth b/awk/forth/test.forth
new file mode 100644
index 0000000..d746e66
--- /dev/null
+++ b/awk/forth/test.forth
@@ -0,0 +1,157 @@
+( Basic Forth test suite )
+
+testing "Basic stack operations"
+5 dup . . test 5 test 5
+
+testing "Addition"
+3 4 + test 7
+
+testing "Subtraction"
+10 3 - test 7
+
+testing "Multiplication"
+6 7 * test 42
+
+testing "Division"
+20 4 / test 5
+
+testing "Stack manipulation - rot"
+1 2 3 rot test 1
+
+testing "Stack manipulation - drop"
+1 2 3 drop test 2
+
+testing "Stack manipulation - nip"
+1 2 3 nip test 3
+
+testing "Stack manipulation - tuck"
+1 2 tuck test 2
+
+testing "Stack manipulation - over"
+1 2 over test 1
+
+testing "Variables"
+variable x
+5 x !
+x @ test 5
+10 x !
+x ? ( should print 10 )
+
+testing "Negate"
+5 negate test -5
+
+testing "Absolute value"
+-7 abs test 7
+
+testing "Maximum"
+3 8 max test 8
+
+testing "Minimum"
+3 8 min test 3
+
+testing "Modulo"
+17 5 mod test 2
+
+testing "Equality"
+5 5 = test 1
+5 6 = test 0
+
+testing "Word definition"
+: square dup * ;
+5 square test 25
+
+testing "Complex word definition"
+variable counter
+: increment-counter counter @ 1 + counter ! ;
+5 counter !
+increment-counter
+counter @ test 6
+
+testing "Basic conditional - if/then"
+: test-if-1 ( n -- n ) dup 5 > if ." Greater than 5" then ;
+6 test-if-1 test 6 ( should print "Greater than 5" and leave 6 )
+4 test-if-1 test 4 ( should print nothing and leave 4 )
+
+testing "Basic conditional - if/else/then"
+: test-if-2 ( n -- n ) dup 5 > if ." Greater" else ." Less=" then ;
+6 test-if-2 test 6 ( should print "Greater" and leave 6 )
+4 test-if-2 test 4 ( should print "Less=" and leave 4 )
+5 test-if-2 test 5 ( should print "Less=" and leave 5 )
+
+testing "Nested conditionals"
+: test-if-3 ( n -- n ) 
+    dup 10 > if 
+        dup 20 > if 
+            ." >20 "
+        then
+        ." >10 "
+    then ;
+25 test-if-3 test 25 ( should print ">20 >10 " and leave 25 )
+15 test-if-3 test 15 ( should print ">10 " and leave 15 )
+5 test-if-3 test 5   ( should print nothing and leave 5 )
+
+testing "Conditional with stack operations"
+: test-if-4 ( n -- n n ) 
+    dup 5 > if 
+        dup 
+    then ;
+6 test-if-4 swap test 6 test 6 ( should leave 6 6 )
+4 test-if-4 test 4 ( should leave just 4 )
+
+testing "Complex nested conditionals"
+: test-if-5 ( n -- n )
+    dup 0 < if
+        ." negative "
+    else
+        dup 100 > if
+            ." big "
+        else 
+            dup 50 > if
+                ." medium "
+            else
+                ." small "
+            then
+        then
+    then ;
+-5 test-if-5 test -5 ( should print "negative " )
+150 test-if-5 test 150 ( should print "big " )
+75 test-if-5 test 75 ( should print "medium " )
+25 test-if-5 test 25 ( should print "small " )
+
+testing "Conditionals in word definitions"
+: abs-test ( n -- |n| ) dup 0 < if negate then ;
+-5 abs-test test 5
+5 abs-test test 5
+
+testing "Complex conditional word"
+: max-test ( n1 n2 -- max ) 
+    2dup > if 
+        drop 
+    else 
+        nip 
+    then ;
+5 3 max-test test 5
+3 5 max-test test 5
+
+( Try to use if outside of a definition - should error )
+testing "Compile-only words"
+5 4 > if 42 then ( should print error about compile-only word )
+
+testing "Comparison operators"
+5 3 > test 1  ( 5 > 3 is true )
+3 5 > test 0  ( 3 > 5 is false )
+3 5 < test 1  ( 3 < 5 is true )
+5 3 < test 0  ( 5 < 3 is false )
+5 5 < test 0  ( 5 < 5 is false )
+5 5 > test 0  ( 5 > 5 is false )
+
+testing "Comparison in conditionals"
+: test-compare ( n -- )
+dup 5 > if ." Greater than 5" else
+dup 5 < if ." Less than 5" else
+." Equal to 5" then then ;
+6 test-compare ( should print "Greater than 5" )
+4 test-compare ( should print "Less than 5" )
+5 test-compare ( should print "Equal to 5" )
+
+bye 
\ No newline at end of file
diff --git a/html/voxels/index.html b/html/isometric-bounce/index.html
index 4f60ff2..570f247 100644
--- a/html/voxels/index.html
+++ b/html/isometric-bounce/index.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
-<html>
+<html lang="en">
 <head>
-    <title>Isometric Game</title>
+    <title>Bouncy Isometric Guy</title>
     <style>
         body {
             margin: 0;
@@ -11,7 +11,7 @@
             display: block;
             width: 100vw;
             height: 100vh;
-            background: #f0f0f0;
+            background: #bce8ff;
         }
     </style>
 </head>
diff --git a/html/isometric-bounce/js/game.js b/html/isometric-bounce/js/game.js
new file mode 100644
index 0000000..a1849c8
--- /dev/null
+++ b/html/isometric-bounce/js/game.js
@@ -0,0 +1,335 @@
+function createGame() {
+    const state = {
+        canvas: document.getElementById('gameCanvas'),
+        ctx: null,
+        gridSize: 10,
+        tileWidth: 50,
+        tileHeight: 25,
+        offsetX: 0,
+        offsetY: 0,
+        particles: [],
+        lastFrameTime: 0,
+        player: {
+            x: 0,
+            y: 0,
+            targetX: 0,
+            targetY: 0,
+            size: 20,
+            path: [],
+            currentWaypoint: null,
+            jumpHeight: 0,
+            jumpProgress: 0,
+            isJumping: false,
+            startX: 0,
+            startY: 0
+        },
+        isHopping: false,
+        hopProgress: 0
+    };
+
+    state.ctx = state.canvas.getContext('2d');
+
+    function toIsometric(x, y) {
+        return {
+            x: (x - y) * state.tileWidth / 2,
+            y: (x + y) * state.tileHeight / 2
+        };
+    }
+
+    function fromIsometric(screenX, screenY) {
+        const adjustedX = screenX - state.offsetX;
+        const adjustedY = screenY - state.offsetY;
+        
+        const x = (adjustedX / state.tileWidth + adjustedY / state.tileHeight) / 1;
+        const y = (adjustedY / state.tileHeight - adjustedX / state.tileWidth) / 1;
+        
+        return { x: Math.round(x), y: Math.round(y) };
+    }
+
+    function resizeCanvas() {
+        state.canvas.width = window.innerWidth;
+        state.canvas.height = window.innerHeight;
+        
+        state.offsetX = state.canvas.width / 2;
+        state.offsetY = state.canvas.height / 3;
+        
+        const minDimension = Math.min(state.canvas.width, state.canvas.height);
+        const scaleFactor = minDimension / 800;
+        state.tileWidth = 50 * scaleFactor;
+        state.tileHeight = 25 * scaleFactor;
+        state.player.size = 20 * scaleFactor;
+    }
+
+    function dustyParticles(x, y) {
+        const particleCount = 12;
+        for (let i = 0; i < particleCount; i++) {
+            const baseAngle = (Math.PI * 2 * i) / particleCount;
+            const randomAngle = baseAngle + (Math.random() - 0.5) * 0.5;
+            
+            const speed = 0.3 + Math.random() * 0.4;
+            const initialSize = (state.player.size * 0.15) + (Math.random() * state.player.size * 0.15);
+            const greyValue = 220 + Math.floor(Math.random() * 35);
+            
+            state.particles.push({
+                x, y,
+                dx: Math.cos(randomAngle) * speed,
+                dy: Math.sin(randomAngle) * speed,
+                life: 0.8 + Math.random() * 0.4,
+                size: initialSize,
+                color: `rgb(${greyValue}, ${greyValue}, ${greyValue})`,
+                initialSize,
+                rotationSpeed: (Math.random() - 0.5) * 0.2,
+                rotation: Math.random() * Math.PI * 2
+            });
+        }
+    }
+
+    function updateParticles() {
+        for (let i = state.particles.length - 1; i >= 0; i--) {
+            const particle = state.particles[i];
+            
+            particle.x += particle.dx;
+            particle.y += particle.dy;
+            particle.dy += 0.01;
+            particle.rotation += particle.rotationSpeed;
+            particle.life -= 0.03;
+            particle.size = particle.initialSize * (particle.life * 1.5);
+            
+            if (particle.life <= 0) {
+                state.particles.splice(i, 1);
+            }
+        }
+    }
+
+    function findPath(startX, startY, endX, endY) {
+        const path = [];
+        
+        if (startX !== endX) {
+            const stepX = startX < endX ? 1 : -1;
+            for (let x = startX + stepX; stepX > 0 ? x <= endX : x >= endX; x += stepX) {
+                path.push({ x, y: startY });
+            }
+        }
+        
+        if (startY !== endY) {
+            const stepY = startY < endY ? 1 : -1;
+            for (let y = startY + stepY; stepY > 0 ? y <= endY : y >= endY; y += stepY) {
+                path.push({ x: endX, y });
+            }
+        }
+
+        return path;
+    }
+
+    function updatePlayer() {
+        const jumpDuration = 0.1;
+        const maxJumpHeight = state.tileHeight;
+
+        if (state.isHopping) {
+            state.hopProgress += jumpDuration;
+            state.hopProgress = Math.min(state.hopProgress, 1);
+            
+            state.player.jumpHeight = Math.sin(state.hopProgress * Math.PI) * maxJumpHeight;
+
+            if (state.hopProgress >= 1) {
+                state.isHopping = false;
+                state.player.jumpHeight = 0;
+            }
+        } else {
+            if (!state.player.currentWaypoint && state.player.path.length > 0) {
+                state.player.currentWaypoint = state.player.path.shift();
+                state.player.isJumping = true;
+                state.player.jumpProgress = 0;
+                state.player.startX = state.player.x;
+                state.player.startY = state.player.y;
+            }
+
+            if (state.player.currentWaypoint && state.player.isJumping) {
+                state.player.jumpProgress += jumpDuration;
+                state.player.jumpProgress = Math.min(state.player.jumpProgress, 1);
+                
+                state.player.jumpHeight = Math.sin(state.player.jumpProgress * Math.PI) * maxJumpHeight;
+                
+                state.player.x = state.player.startX + (state.player.currentWaypoint.x - state.player.startX) * state.player.jumpProgress;
+                state.player.y = state.player.startY + (state.player.currentWaypoint.y - state.player.startY) * state.player.jumpProgress;
+                
+                if (state.player.jumpProgress >= 1) {
+                    state.player.isJumping = false;
+                    state.player.jumpHeight = 0;
+                    state.player.x = state.player.currentWaypoint.x;
+                    state.player.y = state.player.currentWaypoint.y;
+                    dustyParticles(state.player.x, state.player.y);
+                    state.player.currentWaypoint = null;
+                }
+            }
+        }
+    }
+
+    function drawGrid() {
+        for (let x = 0; x < state.gridSize; x++) {
+            for (let y = 0; y < state.gridSize; y++) {
+                const iso = toIsometric(x, y);
+                
+                // Diamonds!
+                state.ctx.beginPath(); // Start a new path
+                state.ctx.moveTo(iso.x + state.offsetX, iso.y + state.offsetY - state.tileHeight/2); // Move to the top point of the diamond
+                state.ctx.lineTo(iso.x + state.offsetX + state.tileWidth/2, iso.y + state.offsetY); // Draw line to the right point of the diamond
+                state.ctx.lineTo(iso.x + state.offsetX, iso.y + state.offsetY + state.tileHeight/2); // Draw line to the bottom point of the diamond
+                state.ctx.lineTo(iso.x + state.offsetX - state.tileWidth/2, iso.y + state.offsetY); // Draw line to the left point of the diamond
+                state.ctx.closePath(); // Close the path to complete the diamond
+                
+                state.ctx.strokeStyle = '#666';
+                state.ctx.stroke();
+                state.ctx.fillStyle = '#fff';
+                state.ctx.fill();
+            }
+        }
+    }
+
+    function drawParticles() {
+        state.particles.forEach(particle => {
+            const iso = toIsometric(particle.x, particle.y);
+            
+            state.ctx.save();
+            state.ctx.translate(iso.x + state.offsetX, iso.y + state.offsetY);
+            state.ctx.rotate(particle.rotation);
+            
+            state.ctx.beginPath();
+            const points = 5;
+            for (let i = 0; i < points * 2; i++) {
+                const angle = (i * Math.PI) / points;
+                const radius = particle.size * (i % 2 ? 0.7 : 1);
+                const x = Math.cos(angle) * radius;
+                const y = Math.sin(angle) * radius;
+                i === 0 ? state.ctx.moveTo(x, y) : state.ctx.lineTo(x, y);
+            }
+            state.ctx.closePath();
+            
+            state.ctx.fillStyle = `rgba(${particle.color.slice(4, -1)}, ${particle.life * 0.5})`;
+            state.ctx.fill();
+            
+            state.ctx.restore();
+        });
+    }
+
+    function drawPlayer() {
+        const iso = toIsometric(state.player.x, state.player.y);
+        const jumpOffset = state.player.jumpHeight || state.player.jumpHeight;
+        
+        let squashStretch = 1;
+        if (state.player.isJumping) {
+            const jumpPhase = Math.sin(state.player.jumpProgress * Math.PI);
+            if (state.player.jumpProgress < 0.2) {
+                squashStretch = 1 + (0.3 * (1 - state.player.jumpProgress / 0.2));
+            } else if (state.player.jumpProgress > 0.8) {
+                squashStretch = 1 - (0.3 * ((state.player.jumpProgress - 0.8) / 0.2));
+            } else {
+                squashStretch = 1 + (0.1 * jumpPhase);
+            }
+        }
+        
+        const shadowScale = Math.max(0.2, 1 - (jumpOffset / state.tileHeight));
+        state.ctx.beginPath();
+        state.ctx.ellipse(
+            iso.x + state.offsetX,
+            iso.y + state.offsetY + 2,
+            state.player.size * 0.8 * shadowScale,
+            state.player.size * 0.3 * shadowScale,
+            0, 0, Math.PI * 2
+        );
+        state.ctx.fillStyle = `rgba(0,0,0,${0.2 * shadowScale})`;
+        state.ctx.fill();
+
+        const bodyHeight = state.player.size * 2 * squashStretch;
+        const bodyWidth = state.player.size * 0.8 * (1 / squashStretch);
+        
+        state.ctx.save();
+        state.ctx.translate(iso.x + state.offsetX, iso.y + state.offsetY - jumpOffset);
+        state.ctx.scale(1, 0.5);
+        state.ctx.fillStyle = '#ff4444';
+        state.ctx.strokeStyle = '#aa0000';
+        state.ctx.fillRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight);
+        state.ctx.strokeRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight);
+        state.ctx.restore();
+
+        state.ctx.beginPath();
+        state.ctx.ellipse(
+            iso.x + state.offsetX,
+            iso.y + state.offsetY - state.player.size * squashStretch - jumpOffset,
+            state.player.size * (1 / squashStretch),
+            state.player.size * 0.5 * squashStretch,
+            0, 0, Math.PI * 2
+        );
+        state.ctx.fillStyle = '#ff4444';
+        state.ctx.fill();
+        state.ctx.strokeStyle = '#aa0000';
+        state.ctx.stroke();
+    }
+
+    function gameLoop(timestamp) {
+
+        const frameInterval = 1000 / 60;
+        
+        if (!state.lastFrameTime || timestamp - state.lastFrameTime >= frameInterval) {
+            state.ctx.clearRect(0, 0, state.canvas.width, state.canvas.height);
+            
+            drawGrid();
+            updateParticles();
+            drawParticles();
+            updatePlayer();
+            drawPlayer();
+            
+            state.lastFrameTime = timestamp;
+        }
+        
+        requestAnimationFrame(gameLoop);
+    }
+
+    function handleClick(e) {
+        const rect = state.canvas.getBoundingClientRect();
+        const clickX = e.clientX - rect.left;
+        const clickY = e.clientY - rect.top;
+        
+        const gridPos = fromIsometric(clickX, clickY);
+        
+        const iso = toIsometric(state.player.x, state.player.y);
+        const playerRadius = state.player.size;
+        const distanceToPlayer = Math.sqrt(
+            Math.pow(clickX - (iso.x + state.offsetX), 2) +
+            Math.pow(clickY - (iso.y + state.offsetY), 2)
+        );
+
+        if (distanceToPlayer < playerRadius) {
+            state.isHopping = true;
+            state.hopProgress = 0;
+        } else if (gridPos.x >= 0 && gridPos.x < state.gridSize &&
+                   gridPos.y >= 0 && gridPos.y < state.gridSize) {
+            state.player.targetX = Math.round(gridPos.x);
+            state.player.targetY = Math.round(gridPos.y);
+            
+            state.player.path = findPath(
+                Math.round(state.player.x),
+                Math.round(state.player.y),
+                state.player.targetX,
+                state.player.targetY
+            );
+            
+            state.player.currentWaypoint = null;
+        }
+    }
+
+    function init() {
+        resizeCanvas();
+        window.addEventListener('resize', resizeCanvas);
+        state.canvas.addEventListener('click', handleClick);
+        state.lastFrameTime = 0;
+        gameLoop();
+    }
+
+    return { init };
+}
+
+window.onload = () => {
+    const game = createGame();
+    game.init();
+};
\ No newline at end of file
diff --git a/html/voxels/js/game.js b/html/voxels/js/game.js
deleted file mode 100644
index 4547634..0000000
--- a/html/voxels/js/game.js
+++ /dev/null
@@ -1,345 +0,0 @@
-class IsometricGame {
-    constructor() {
-        this.canvas = document.getElementById('gameCanvas');
-        this.ctx = this.canvas.getContext('2d');
-        
-        // Grid properties
-        this.gridSize = 10;
-        this.tileWidth = 50;
-        this.tileHeight = 25;
-        
-        // Player properties
-        this.player = {
-            x: 0,
-            y: 0,
-            targetX: 0,
-            targetY: 0,
-            size: 20,
-            path: [], // Array to store waypoints
-            currentWaypoint: null,
-            jumpHeight: 0,
-            jumpProgress: 0,
-            isJumping: false,
-            startX: 0,
-            startY: 0
-        };
-        
-        // Add particle system
-        this.particles = [];
-        
-        // Handle window resize
-        this.resizeCanvas();
-        window.addEventListener('resize', () => this.resizeCanvas());
-        
-        this.setupEventListeners();
-        this.gameLoop();
-    }
-    
-    resizeCanvas() {
-        this.canvas.width = window.innerWidth;
-        this.canvas.height = window.innerHeight;
-        
-        // Recalculate grid offset to center it
-        this.offsetX = this.canvas.width / 2;
-        this.offsetY = this.canvas.height / 3;
-        
-        // Scale tile size based on screen size
-        const minDimension = Math.min(this.canvas.width, this.canvas.height);
-        const scaleFactor = minDimension / 800; // 800 is our reference size
-        this.tileWidth = 50 * scaleFactor;
-        this.tileHeight = 25 * scaleFactor;
-        this.player.size = 20 * scaleFactor;
-    }
-    
-    toIsometric(x, y) {
-        return {
-            x: (x - y) * this.tileWidth / 2,
-            y: (x + y) * this.tileHeight / 2
-        };
-    }
-    
-    fromIsometric(screenX, screenY) {
-        // Convert screen coordinates back to grid coordinates
-        screenX -= this.offsetX;
-        screenY -= this.offsetY;
-        
-        const x = (screenX / this.tileWidth + screenY / this.tileHeight) / 1;
-        const y = (screenY / this.tileHeight - screenX / this.tileWidth) / 1;
-        
-        return { x: Math.round(x), y: Math.round(y) };
-    }
-    
-    drawGrid() {
-        for (let x = 0; x < this.gridSize; x++) {
-            for (let y = 0; y < this.gridSize; y++) {
-                const iso = this.toIsometric(x, y);
-                
-                // Draw tile
-                this.ctx.beginPath();
-                this.ctx.moveTo(iso.x + this.offsetX, iso.y + this.offsetY - this.tileHeight/2);
-                this.ctx.lineTo(iso.x + this.offsetX + this.tileWidth/2, iso.y + this.offsetY);
-                this.ctx.lineTo(iso.x + this.offsetX, iso.y + this.offsetY + this.tileHeight/2);
-                this.ctx.lineTo(iso.x + this.offsetX - this.tileWidth/2, iso.y + this.offsetY);
-                this.ctx.closePath();
-                
-                this.ctx.strokeStyle = '#666';
-                this.ctx.stroke();
-                this.ctx.fillStyle = '#fff';
-                this.ctx.fill();
-            }
-        }
-    }
-    
-    drawPlayer() {
-        // Convert player grid position to isometric coordinates
-        const iso = this.toIsometric(this.player.x, this.player.y);
-        
-        // Apply jump height offset
-        const jumpOffset = this.player.jumpHeight || 0;
-        
-        // Draw player shadow (gets smaller when jumping)
-        const shadowScale = Math.max(0.2, 1 - (jumpOffset / this.tileHeight));
-        this.ctx.beginPath();
-        this.ctx.ellipse(
-            iso.x + this.offsetX,
-            iso.y + this.offsetY + 2,
-            this.player.size * 0.8 * shadowScale,
-            this.player.size * 0.3 * shadowScale,
-            0,
-            0,
-            Math.PI * 2
-        );
-        this.ctx.fillStyle = `rgba(0,0,0,${0.2 * shadowScale})`;
-        this.ctx.fill();
-
-        // Draw player body with jump offset
-        this.ctx.beginPath();
-        this.ctx.fillStyle = '#ff4444';
-        this.ctx.strokeStyle = '#aa0000';
-        
-        const bodyHeight = this.player.size * 2;
-        const bodyWidth = this.player.size * 0.8;
-        
-        this.ctx.save();
-        this.ctx.translate(iso.x + this.offsetX, iso.y + this.offsetY - jumpOffset);
-        this.ctx.scale(1, 0.5);
-        this.ctx.fillRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight);
-        this.ctx.strokeRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight);
-        this.ctx.restore();
-
-        // Draw player head with jump offset
-        this.ctx.beginPath();
-        this.ctx.ellipse(
-            iso.x + this.offsetX,
-            iso.y + this.offsetY - this.player.size - jumpOffset,
-            this.player.size,
-            this.player.size * 0.5,
-            0,
-            0,
-            Math.PI * 2
-        );
-        this.ctx.fillStyle = '#ff4444';
-        this.ctx.fill();
-        this.ctx.strokeStyle = '#aa0000';
-        this.ctx.stroke();
-    }
-    
-    findPath(startX, startY, endX, endY) {
-        // Simple pathfinding that follows grid edges
-        const path = [];
-        
-        // First move along X axis
-        if (startX !== endX) {
-            const stepX = startX < endX ? 1 : -1;
-            for (let x = startX + stepX; stepX > 0 ? x <= endX : x >= endX; x += stepX) {
-                path.push({ x: x, y: startY });
-            }
-        }
-        
-        // Then move along Y axis
-        if (startY !== endY) {
-            const stepY = startY < endY ? 1 : -1;
-            for (let y = startY + stepY; stepY > 0 ? y <= endY : y >= endY; y += stepY) {
-                path.push({ x: endX, y: y });
-            }
-        }
-
-        return path;
-    }
-    
-    updatePlayer() {
-        const jumpDuration = 0.1; // Faster jump for snappier movement
-        const maxJumpHeight = this.tileHeight;
-
-        // If we don't have a current waypoint but have a path, get next waypoint
-        if (!this.player.currentWaypoint && this.player.path.length > 0) {
-            this.player.currentWaypoint = this.player.path.shift();
-            this.player.isJumping = true;
-            this.player.jumpProgress = 0;
-            
-            // Store starting position for interpolation
-            this.player.startX = this.player.x;
-            this.player.startY = this.player.y;
-        }
-
-        // Move towards current waypoint
-        if (this.player.currentWaypoint) {
-            // Update jump animation
-            if (this.player.isJumping) {
-                this.player.jumpProgress += jumpDuration;
-                
-                // Clamp progress to 1
-                if (this.player.jumpProgress > 1) this.player.jumpProgress = 1;
-                
-                // Parabolic jump arc
-                this.player.jumpHeight = Math.sin(this.player.jumpProgress * Math.PI) * maxJumpHeight;
-                
-                // Precise interpolation between points
-                this.player.x = this.player.startX + (this.player.currentWaypoint.x - this.player.startX) * this.player.jumpProgress;
-                this.player.y = this.player.startY + (this.player.currentWaypoint.y - this.player.startY) * this.player.jumpProgress;
-                
-                // Landing
-                if (this.player.jumpProgress >= 1) {
-                    this.player.isJumping = false;
-                    this.player.jumpHeight = 0;
-                    this.player.x = this.player.currentWaypoint.x;
-                    this.player.y = this.player.currentWaypoint.y;
-                    this.createDustParticles(this.player.x, this.player.y);
-                    this.player.currentWaypoint = null;
-                }
-            }
-        }
-    }
-    
-    setupEventListeners() {
-        this.canvas.addEventListener('click', (e) => {
-            const rect = this.canvas.getBoundingClientRect();
-            const clickX = e.clientX - rect.left;
-            const clickY = e.clientY - rect.top;
-            
-            const gridPos = this.fromIsometric(clickX, clickY);
-            
-            // Only move if within grid bounds
-            if (gridPos.x >= 0 && gridPos.x < this.gridSize &&
-                gridPos.y >= 0 && gridPos.y < this.gridSize) {
-                
-                // Set target and calculate path
-                this.player.targetX = Math.round(gridPos.x);
-                this.player.targetY = Math.round(gridPos.y);
-                
-                // Calculate new path
-                this.player.path = this.findPath(
-                    Math.round(this.player.x),
-                    Math.round(this.player.y),
-                    this.player.targetX,
-                    this.player.targetY
-                );
-                
-                // Clear current waypoint to start new path
-                this.player.currentWaypoint = null;
-            }
-        });
-    }
-    
-    gameLoop() {
-        // Clear canvas
-        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
-        
-        // Draw game elements
-        this.drawGrid();
-        this.updateParticles();
-        this.drawParticles();
-        this.updatePlayer();
-        this.drawPlayer();
-        
-        // Continue game loop
-        requestAnimationFrame(() => this.gameLoop());
-    }
-
-    // Add new particle system methods
-    createDustParticles(x, y) {
-        const particleCount = 12; // Increased for more particles
-        for (let i = 0; i < particleCount; i++) {
-            // Randomize the angle slightly
-            const baseAngle = (Math.PI * 2 * i) / particleCount;
-            const randomAngle = baseAngle + (Math.random() - 0.5) * 0.5;
-            
-            // Random speed and size variations
-            const speed = 0.3 + Math.random() * 0.4;
-            const initialSize = (this.player.size * 0.15) + (Math.random() * this.player.size * 0.15);
-            
-            // Random grey color
-            const greyValue = 220 + Math.floor(Math.random() * 35);
-            const color = `rgb(${greyValue}, ${greyValue}, ${greyValue})`;
-            
-            this.particles.push({
-                x: x,
-                y: y,
-                dx: Math.cos(randomAngle) * speed,
-                dy: Math.sin(randomAngle) * speed,
-                life: 0.8 + Math.random() * 0.4, // Random initial life
-                size: initialSize,
-                color: color,
-                initialSize: initialSize,
-                rotationSpeed: (Math.random() - 0.5) * 0.2,
-                rotation: Math.random() * Math.PI * 2
-            });
-        }
-    }
-
-    updateParticles() {
-        for (let i = this.particles.length - 1; i >= 0; i--) {
-            const particle = this.particles[i];
-            
-            // Update position with slight gravity effect
-            particle.x += particle.dx;
-            particle.y += particle.dy;
-            particle.dy += 0.01; // Slight upward drift
-            
-            // Update rotation
-            particle.rotation += particle.rotationSpeed;
-            
-            // Non-linear fade out
-            particle.life -= 0.03;
-            particle.size = particle.initialSize * (particle.life * 1.5); // Grow slightly as they fade
-            
-            // Remove dead particles
-            if (particle.life <= 0) {
-                this.particles.splice(i, 1);
-            }
-        }
-    }
-
-    drawParticles() {
-        for (const particle of this.particles) {
-            const iso = this.toIsometric(particle.x, particle.y);
-            
-            this.ctx.save();
-            this.ctx.translate(iso.x + this.offsetX, iso.y + this.offsetY);
-            this.ctx.rotate(particle.rotation);
-            
-            // Draw a slightly irregular dust puff
-            this.ctx.beginPath();
-            const points = 5;
-            for (let i = 0; i < points * 2; i++) {
-                const angle = (i * Math.PI) / points;
-                const radius = particle.size * (i % 2 ? 0.7 : 1);
-                const x = Math.cos(angle) * radius;
-                const y = Math.sin(angle) * radius;
-                if (i === 0) this.ctx.moveTo(x, y);
-                else this.ctx.lineTo(x, y);
-            }
-            this.ctx.closePath();
-            
-            this.ctx.fillStyle = `rgba(${particle.color.slice(4, -1)}, ${particle.life * 0.5})`;
-            this.ctx.fill();
-            
-            this.ctx.restore();
-        }
-    }
-}
-
-// Start the game when the page loads
-window.onload = () => {
-    new IsometricGame();
-};
\ No newline at end of file