2022-11-12 13:47:48 -05:00
//URL
urlParams = new URLSearchParams ( window . location . search ) ;
//Objects
//getKeyByValue code by SO UncleLaz: https://stackoverflow.com/questions/9907419/how-to-get-a-key-in-a-javascript-object-by-its-value
//CC-BY-SA-4.0
function getKeyByValue ( object , value ) {
return Object . keys ( object ) . find ( key => object [ key ] === value ) ;
} ;
//RNG
//Random integer from 0 to n
function randomIntegerFromZeroToValue ( value ) {
var absoluteValuePlusOne = Math . abs ( value ) + 1 ;
if ( value >= 0 ) { //Positive case
return Math . floor ( Math . random ( ) * absoluteValuePlusOne )
} else { //Negative case: flip sign
return 0 - Math . floor ( Math . random ( ) * absoluteValuePlusOne )
} ;
} ;
//Random thing from array
function randomChoice ( array ) {
if ( array . length === 0 ) { throw new Error ( ` The array ${ array } is empty ` ) } ;
var length = array . length ;
2022-11-12 15:13:40 -05:00
var randomIndex = randomIntegerFromZeroToValue ( length - 1 ) ;
2022-11-12 13:47:48 -05:00
return array [ randomIndex ] ;
} ;
//Random integer from m to n
function randomIntegerBetweenTwoValues ( min , max ) {
if ( min > max ) {
var temp = max ; //the need of a temporary space has always annoyed me
max = min ;
min = temp ;
} ;
return Math . floor ( Math . random ( ) * ( max - min + 1 ) ) + min
} ;
2023-02-21 16:52:10 -05:00
//cyrb128 idk where this comes from but it was in the same thread
function cyrb128 ( str ) {
let h1 = 1779033703 , h2 = 3144134277 ,
h3 = 1013904242 , h4 = 2773480762 ;
for ( let i = 0 , k ; i < str . length ; i ++ ) {
k = str . charCodeAt ( i ) ;
h1 = h2 ^ Math . imul ( h1 ^ k , 597399067 ) ;
h2 = h3 ^ Math . imul ( h2 ^ k , 2869860233 ) ;
h3 = h4 ^ Math . imul ( h3 ^ k , 951274213 ) ;
h4 = h1 ^ Math . imul ( h4 ^ k , 2716044179 ) ;
}
h1 = Math . imul ( h3 ^ ( h1 >>> 18 ) , 597399067 ) ;
h2 = Math . imul ( h4 ^ ( h2 >>> 22 ) , 2869860233 ) ;
h3 = Math . imul ( h1 ^ ( h3 >>> 17 ) , 951274213 ) ;
h4 = Math . imul ( h2 ^ ( h4 >>> 19 ) , 2716044179 ) ;
return [ ( h1 ^ h2 ^ h3 ^ h4 ) >>> 0 , ( h2 ^ h1 ) >>> 0 , ( h3 ^ h1 ) >>> 0 , ( h4 ^ h1 ) >>> 0 ] ;
}
2023-02-21 16:33:13 -05:00
function mulberry32 ( a ) { //Mulberry32 implemented in JS from StackOverflow, https://gist.github.com/tommyettinger/46a874533244883189143505d203312c
return function ( ) {
var t = a += 0x6D2B79F5 ;
t = Math . imul ( t ^ t >>> 15 , t | 1 ) ;
t ^= t + Math . imul ( t ^ t >>> 7 , t | 61 ) ;
return ( ( t ^ t >>> 14 ) >>> 0 ) / 4294967296 ;
}
} //returns random function seeded with a
2023-02-22 07:32:50 -05:00
//Seeded randbetween
function seededRandBetween ( min , max , randomFunction ) {
if ( min > max ) {
var temp = max ;
max = min ;
min = temp ;
} ;
return Math . floor ( randomFunction ( ) * ( max - min + 1 ) ) + min
} ;
2022-11-12 13:47:48 -05:00
//Arrays
//Shallow array comparer by SO Tim Down: https://stackoverflow.com/a/10260204
//CC-BY-SA-3.0
function arraysIdentical ( arr1 , arr2 ) {
var i = arr1 . length ;
if ( i !== arr2 . length ) {
return false ;
} ;
while ( i -- ) {
if ( arr1 [ i ] !== arr2 [ i ] ) {
return false ;
} ;
} ;
return true ;
} ;
function indexOf ( arr , val , comparer ) {
for ( var i = 0 , len = arr . length ; i < len ; ++ i ) {
if ( i in arr && comparer ( arr [ i ] , val ) ) {
return i ;
} ;
} ;
return - 1 ;
} ;
function averageNumericArray ( array ) {
var total = array . reduce ( addTwoNumbers , 0 )
return total / array . length
} ;
2022-11-12 15:37:41 -05:00
function sumNumericArray ( array ) { //Sum of array numbers
return array . reduce ( ( partialSum , a ) => partialSum + a , 0 ) ;
} ;
function pad _array ( arr , len , fill ) { //https://stackoverflow.com/a/38851957
//console.log("Padding array");
return arr . concat ( Array ( len ) . fill ( fill ) ) . slice ( 0 , len ) ;
}
2022-11-12 13:47:48 -05:00
//Function to check if an array includes a given array by SO Johnny Tisdale: https://stackoverflow.com/a/60922255
//CC-BY-SA-4.0
function includesArray ( parentArray , testArray ) {
for ( let i = 0 ; i < parentArray . length ; i ++ ) {
if ( parentArray [ i ] . every ( function ( value , index ) { return value === testArray [ index ] } ) ) {
return true ;
} ;
} ;
return false ;
} ;
2022-11-12 15:37:41 -05:00
function addArraysInPairs ( array1 , array2 , fill = 0 ) { //e.g. [1,2,3] + [10,0,-1] = [11,2,2]
//console.log("Adding in pairs: " + array1 + " and " + array2 + ".");
if ( array1 . length > array2 . length ) { //zero-padding
array2 = pad _array ( array2 , array1 . length , fill ) ; //if a1 is longer, pad a2 to a1's length
} else if ( array2 . length > array1 . length ) {
array1 = pad _array ( array1 , array2 . length , fill ) ; //if a2 is longer, pad a1 to a2's length
} ;
var tempArray = [ ] ;
for ( z = 0 ; z < array1 . length ; z ++ ) {
//console.log("Forming output values (" + array1[z] + " + " + array2[z] + ")");
tempArray [ z ] = array1 [ z ] + array2 [ z ] ;
//console.log("Sum" + tempArray[z]);
} ;
//console.log("Added into " + tempArray + ".");
return tempArray ;
} ;
2022-12-20 10:00:52 -05:00
function tryJoin ( stringOrArray , joiner ) {
//console.log(`tryJoin: ${stringOrArray}`);
if ( typeof ( stringOrArray ) === "string" ) {
//console.log("tryJoin: String");
return stringOrArray ;
} else if ( Array . isArray ( stringOrArray ) ) {
//console.log("tryJoin: Array");
return stringOrArray . join ( joiner ) ;
} else {
throw new TypeError ( ` Unexpected type: ${ typeof ( stringOrArray ) } ` ) ;
} ;
} ;
2022-11-12 13:47:48 -05:00
//Checks
//Element exists in the elements object
function elementExists ( elementName ) {
return typeof ( elements [ elementName ] ) === "object" ;
} ;
2023-01-23 17:14:59 -05:00
//Has a given state
function isState ( elementName , inputState ) {
if ( ! elementExists ( elementName ) ) {
throw new Error ( ` Element ${ elementName } doesn't exist ` ) ;
} ;
var infoState = elements [ elementName ] . state ;
if ( infoState == undefined ) { infoState = "undefined" } ;
if ( inputState == undefined ) { inputState = "undefined" } ;
if ( inputState instanceof Array ) {
var limit = 0 ;
while ( inputState . includes ( undefined ) && limit < 3 ) {
inputState [ inputState . indexOf ( undefined ) ] = "undefined"
limit ++ ;
} ;
} ;
if ( inputState instanceof Array ) {
return inputState . includes ( infoState ) ;
} ;
return infoState == inputState ;
} ;
2022-11-12 13:47:48 -05:00
//Check if pixel of given element exists at given location
function hasPixel ( x , y , elementInput ) {
if ( isEmpty ( x , y , true ) ) { //if empty, it can't have a pixel
return false ;
} else {
if ( elementInput . includes ( "," ) ) { //CSTA
elementInput = elementInput . split ( "," ) ;
} ;
if ( Array . isArray ( elementInput ) ) { //if element list
for ( i = 0 ; i < elementInput . length ; i ++ ) { if ( ! elementExists ( elementInput [ i ] ) ) { console . log ( ` hasPixel: Element " ${ elementInput [ i ] } " doesn't exist ` ) } } ;
return elementInput . includes ( pixelMap [ x ] [ y ] . element ) ;
} else { //if single element
if ( ! elementExists ( elementInput ) ) { console . log ( ` hasPixel: Element " ${ elementInput } " doesn't exist ` ) } ;
return pixelMap [ x ] [ y ] . element === elementInput ;
} ;
} ;
} ;
2023-02-12 10:24:44 -05:00
//Is movable
var backupCategoryWhitelist = [ "land" , "powders" , "weapons" , "food" , "life" , "corruption" , "states" , "fey" , "Fantastic Creatures" , "dyes" , "energy liquids" , "random liquids" , "random gases" , "random rocks" ] ;
var backupElementWhitelist = [ "mercury" , "chalcopyrite_ore" , "chalcopyrite_dust" , "copper_concentrate" , "fluxed_copper_concentrate" , "unignited_pyrestone" , "ignited_pyrestone" , "everfire_dust" , "extinguished_everfire_dust" , "mistake" , "polusium_oxide" , "vaporized_polusium_oxide" , "glowstone_dust" , "redstone_dust" , "soul_mud" , "wet_soul_sand" , "nitrogen_snow" , "fusion_catalyst" , "coal" , "coal_coke" , "blast_furnace_fuel" , "molten_mythril" ] ;
function commonMovableCriteria ( name , shallowBlacklist = null ) {
if ( typeof ( elements [ name ] ) !== "object" ) {
throw new Error ( ` Nonexistent element ${ name } ` ) ;
} ;
var info = elements [ name ] ;
//console.log(`${name} (${JSON.stringify(elements[name])})`);
if ( typeof ( info . state ) === "undefined" ) {
var state = null ;
} else {
var state = info . state ;
} ;
if ( typeof ( info . category ) === "undefined" ) {
var category = "other" ;
} else {
var category = info . category ;
} ;
if ( shallowBlacklist !== null && shallowBlacklist . includes ( name ) ) {
2023-02-12 10:34:31 -05:00
return false ;
} ;
if ( elements [ name ] . tool ) {
return false ;
2023-02-12 10:24:44 -05:00
} ;
if ( elements [ name ] . behavior && elements [ name ] . behavior . toString ( ) == elements . wall . behavior . toString ( ) && ! elements [ name ] . tick ) {
return false ;
} ;
if ( [ "liquid" , "gas" ] . includes ( state ) ) {
return true ;
} ;
if ( info . movable ) {
return true ;
} ;
2023-02-12 10:34:31 -05:00
if ( elements [ name ] . behavior instanceof Array ) {
var behaviorString = elements [ name ] . behavior . toString ( ) ;
return behaviorString . includes ( "M1" ) || behaviorString . includes ( "M2" ) ;
} ;
2023-02-12 10:24:44 -05:00
if ( backupCategoryWhitelist . includes ( category ) ) {
return true ;
} ;
if ( backupElementWhitelist . includes ( name ) ) {
return true ;
} ;
if ( category . includes ( "mudstone" ) ) {
return true ;
} ;
return false ;
} ;
2022-11-12 13:47:48 -05:00
//Math(s)
2023-02-20 15:04:48 -05:00
//Base n logarithm from https://stackoverflow.com/a/3019290
function logN ( number , base ) { //Vulnerable to float issues
return Math . log ( number ) / Math . log ( base ) ;
} ;
2022-11-12 13:47:48 -05:00
//Distance between points
function pyth ( xA , yA , xB , yB ) {
var a = Math . abs ( xB - xA ) ;
var b = Math . abs ( yB - yA ) ;
var c = Math . sqrt ( a * * 2 + b * * 2 ) ;
return c ;
} ;
//Limit number to [min, max]
function bound ( number , lowerBound , upperBound ) {
return Math . min ( upperBound , Math . max ( lowerBound , number ) ) ;
} ;
function addTwoNumbers ( number1 , number2 ) { //reducer
return number1 + number2
}
2023-02-23 10:12:56 -05:00
//Logistic curve
//x = real number
//L = maximum value
//x_0 = "the x value of the sigmoid midpoint" i.e. the x center of the bendy part
//k = steepness
function logisticCurve ( x , L , k , x0 ) {
return L / ( 1 + ( Math . E * * ( - k * ( x - x0 ) ) ) ) ;
} ;
// https://stackoverflow.com/questions/10756313/javascript-jquery-map-a-range-of-numbers-to-another-range-of-numbers
// Function from August Miller
//Map a range of numbers to another range of numbers
function scale ( number , inMin , inMax , outMin , outMax ) {
return ( number - inMin ) * ( outMax - outMin ) / ( inMax - inMin ) + outMin ;
}
2022-11-12 13:47:48 -05:00
//Color
function rgbStringToUnvalidatedObject ( string ) { //turns rgb() to {r,g,b} with no bounds checking
//console.log("Splitting string into object");
string = string . split ( "," ) ;
var red = parseFloat ( string [ 0 ] . substring ( 4 ) ) ;
var green = parseFloat ( string [ 1 ] ) ;
var blue = parseFloat ( string [ 2 ] . slice ( 0 , - 1 ) ) ;
//console.log("String split: outputs " + red + ", " + green + ", " + blue + ".");
return { r : red , g : green , b : blue } ;
} ;
function rgbStringToObject ( string , doRounding = true , doBounding = true ) { //turns rgb() to {r,g,b}
2022-11-13 11:37:30 -05:00
//console.log(`rgbStringToObject: ${string}`);
2022-11-12 13:47:48 -05:00
//console.log("Splitting string into object");
string = string . split ( "," ) ;
if ( ( ! string [ 0 ] . startsWith ( "rgb(" ) ) || ( ! string [ 2 ] . endsWith ( ")" ) ) ) {
throw new Error ( "Color must start with \"rgb(\" and end with \")\"" ) ;
} ;
var red = parseFloat ( string [ 0 ] . substring ( 4 ) ) ;
var green = parseFloat ( string [ 1 ] ) ;
var blue = parseFloat ( string [ 2 ] . slice ( 0 , - 1 ) ) ;
//console.log(`Colors loaded (${red}, ${green}, ${blue})`);
//NaN checking
var redNaN = isNaN ( red ) ;
var greenNaN = isNaN ( green ) ;
var blueNaN = isNaN ( blue ) ;
var NanErrorString = "One or more colors are NaN:"
if ( redNaN ) { NanErrorString += " red" } ;
if ( greenNaN ) { NanErrorString += " green" } ;
if ( blueNaN ) { NanErrorString += " blue" } ;
if ( redNaN || greenNaN || blueNaN ) { throw new Error ( NanErrorString ) } ;
if ( doRounding ) {
red = Math . round ( red ) ;
green = Math . round ( green ) ;
blue = Math . round ( blue ) ;
//console.log(`Colors rounded to (${red}, ${green}, ${blue})`);
} ;
if ( doBounding ) {
red = bound ( red , 0 , 255 )
green = bound ( green , 0 , 255 )
blue = bound ( blue , 0 , 255 )
//console.log(`Colors bounded to (${red}, ${green}, ${blue})`);
} ;
//console.log("String split: outputs " + red + ", " + green + ", " + blue + ".");
return { r : red , g : green , b : blue } ;
} ;
function hslColorStringToObject ( color ) {
if ( ! color . startsWith ( "hsl(" ) || ! color . endsWith ( ")" ) ) {
throw new Error ( ` The color ${ color } is not a valid hsl() color ` )
} ;
var colorTempArray = color . split ( "," )
if ( colorTempArray . length !== 3 ) {
throw new Error ( ` The color ${ color } is not a valid hsl() color ` )
} ;
if ( ! colorTempArray [ 1 ] . endsWith ( "%" ) ) { console . log ( ` hslColorStringToObject: Saturation in color ${ color } was missing a % ` ) ; colorTempArray [ 1 ] += "%" ; }
if ( ! colorTempArray [ 2 ] . endsWith ( "%)" ) ) { console . log ( ` hslColorStringToObject: Lightness in color ${ color } was missing a % ` ) ; colorTempArray [ 2 ] = [ colorTempArray [ 2 ] . slice ( 0 , colorTempArray [ 2 ] . length - 1 ) , "%" , colorTempArray [ 2 ] . slice ( colorTempArray [ 2 ] . length - 1 ) ] . join ( '' ) ; }
var hue = parseFloat ( colorTempArray [ 0 ] . substring ( 4 ) ) ;
var saturation = parseFloat ( colorTempArray [ 1 ] . slice ( 0 , - 1 ) )
var lightness = parseFloat ( colorTempArray [ 2 ] . slice ( 0 , - 2 ) ) ;
//NaN checking
var hueNaN , saturationNaN , lightnessNaN ;
isNaN ( hue ) ? hueNaN = true : hueNaN = false ;
isNaN ( saturation ) ? saturationNaN = true : saturationNaN = false ;
isNaN ( lightness ) ? lightnessNaN = true : lightnessNaN = false ;
var NanErrorString = "One or more colors are NaN:"
if ( hueNaN ) { NanErrorString += " hue" } ;
if ( saturationNaN ) { NanErrorString += " saturation" } ;
if ( lightnessNaN ) { NanErrorString += " lightness" } ;
if ( hueNaN || saturationNaN || lightnessNaN ) { throw new Error ( NanErrorString ) } ;
return { h : hue , s : saturation , l : lightness } ;
} ;
function rgbToHex ( color ) {
2022-11-13 11:37:30 -05:00
//console.log(`rgbToHex called on ${typeof(color) === "object" ? JSON.stringify(color) : color}`);
2022-11-12 13:47:48 -05:00
if ( typeof ( color ) == "object" ) { //Expects object like "{r: 172, g: 11, b: 34}"
var red = color . r ;
var green = color . g ;
var blue = color . b ;
//console.log(`Colors loaded (${red}, ${green}, ${blue})`);
red = Math . round ( red ) ;
green = Math . round ( green ) ;
blue = Math . round ( blue ) ;
//console.log(`Colors rounded to (${red}, ${green}, ${blue})`);
red = bound ( red , 0 , 255 )
green = bound ( green , 0 , 255 )
blue = bound ( blue , 0 , 255 )
//console.log(`Colors bounded to (${red}, ${green}, ${blue})`);
red = red . toString ( 16 ) ;
green = green . toString ( 16 ) ;
blue = blue . toString ( 16 ) ;
//console.log(`Colors converted to (0x${red}, 0x${green}, 0x${blue})`);
//console.log("Padding R");
while ( red . length < 2 ) {
red = "0" + red ;
} ;
//console.log("Padding G");
while ( green . length < 2 ) {
green = "0" + green ;
} ;
//console.log("Padding B");
while ( blue . length < 2 ) {
blue = "0" + blue ;
} ;
//console.log(`Colors padded to (0x${red}, 0x${green}, 0x${blue}), concatenating...`);
return "#" + red + green + blue ;
} else if ( typeof ( color ) == "string" ) { //Expects string like "rgb(20,137,4)". Also doesn't round properly for some reason...
//console.log("Splitting string")
2022-11-12 15:08:56 -05:00
color = rgbStringToUnvalidatedObject ( color ) ;
2022-11-12 13:47:48 -05:00
red = color . r ;
green = color . g ;
blue = color . b ;
//console.log(`Colors loaded (${red}, ${green}, ${blue})`);
red = Math . round ( red ) ;
green = Math . round ( green ) ;
blue = Math . round ( blue ) ;
//console.log(`Colors rounded to (${red}, ${green}, ${blue})`);
red = bound ( red , 0 , 255 )
green = bound ( green , 0 , 255 )
blue = bound ( blue , 0 , 255 )
//console.log(`Colors bounded to (${red}, ${green}, ${blue})`);
red = red . toString ( 16 ) ;
green = green . toString ( 16 ) ;
blue = blue . toString ( 16 ) ;
//console.log(`Colors converted to (0x${red}, 0x${green}, 0x${blue})`);
//console.log("Padding R");
while ( red . length < 2 ) {
red = "0" + red ;
} ;
//console.log("Padding G");
while ( green . length < 2 ) {
green = "0" + green ;
} ;
//console.log("Padding B");
while ( blue . length < 2 ) {
blue = "0" + blue ;
} ;
//console.log(`Colors padded to (0x${red}, 0x${green}, 0x${blue}), concatenating...`);
return "#" + red + green + blue ;
} else {
throw new Error ( ` Received invalid color: ${ color } ` ) ;
} ;
} ;
function linearBlendTwoColorObjects ( color1 , color2 , weight1 = 0.5 ) { / * t h i r d a r g u m e n t i s f o r c o l o r 1 a n d e x p e c t s a f l o a t f r o m 0
to 1 , where 0 means "all color2" and 1 means "all color1" * /
var w1 = Math . min ( Math . max ( weight1 , 0 ) , 1 ) ;
var red1 = color1 . r ;
var green1 = color1 . g ;
var blue1 = color1 . b ;
var red2 = color2 . r ;
var green2 = color2 . g ;
var blue2 = color2 . b ;
var red3 = ( red1 * w1 ) + ( red2 * ( 1 - w1 ) ) ;
var green3 = ( green1 * w1 ) + ( green2 * ( 1 - w1 ) ) ;
var blue3 = ( blue1 * w1 ) + ( blue2 * ( 1 - w1 ) ) ;
return { r : red3 , g : green3 , b : blue3 } ;
} ;
function lightenColor ( color , offset , outputType = "rgb" ) {
if ( typeof ( color ) === "string" ) {
if ( color . length < 10 ) {
//console.log(`detected as hex: ${color}`);
//catch missing octothorpes
if ( ! color . startsWith ( "#" ) ) {
color = "#" + color ;
} ;
//console.log(`octothorpe checked: ${color}`);
offset = parseFloat ( offset ) ;
if ( isNaN ( offset ) ) {
throw new Error ( "Offset is NaN" ) ;
} ;
color = hexToRGB ( color ) ;
if ( color === null ) {
throw new Error ( "hexToRGB(color) was null (maybe it's an invalid hex triplet?)" ) ;
} ;
//console.log("converted color: " + JSON.stringify(color));
var red = color . r + offset ;
var green = color . g + offset ;
var blue = color . b + offset ;
//console.log(`altered color: rgb(${red},${green},${blue})`);
//rounding and bounding
red = Math . round ( red ) ;
green = Math . round ( green ) ;
blue = Math . round ( blue ) ;
//console.log(`rounded color: rgb(${red},${green},${blue})`);
red = bound ( red , 0 , 255 )
green = bound ( green , 0 , 255 )
blue = bound ( blue , 0 , 255 )
//console.log(`bounded color: rgb(${red},${green},${blue})`);
color = { r : red , g : green , b : blue } ;
switch ( outputType . toLowerCase ( ) ) {
case "rgb" :
return ` rgb( ${ red } , ${ green } , ${ blue } ) ` ;
break ;
case "hex" :
return rgbToHex ( color ) ;
break ;
case "json" :
return color ;
break ;
default :
throw new Error ( "outputType must be \"rgb\", \"hex\", \"json\"" ) ;
} ;
} else {
if ( color . startsWith ( "rgb(" ) ) {
2022-11-13 11:37:30 -05:00
color = convertColorFormats ( color , "json" ) ; //object conversion
2022-11-12 13:47:48 -05:00
//console.log(`color converted to object: ${JSON.stringify(color)}`);
offset = parseFloat ( offset ) ;
if ( isNaN ( offset ) ) {
throw new Error ( "Offset is NaN" ) ;
} ;
var red = color . r + offset ;
var green = color . g + offset ;
var blue = color . b + offset ;
//console.log(`altered color: rgb(${red},${green},${blue})`);
//rounding and bounding
red = Math . round ( red ) ;
green = Math . round ( green ) ;
blue = Math . round ( blue ) ;
//console.log(`rounded color: rgb(${red},${green},${blue})`);
red = bound ( red , 0 , 255 )
green = bound ( green , 0 , 255 )
blue = bound ( blue , 0 , 255 )
//console.log(`bounded color: rgb(${red},${green},${blue})`);
color = { r : red , g : green , b : blue } ;
switch ( outputType . toLowerCase ( ) ) {
case "rgb" :
return ` rgb( ${ red } , ${ green } , ${ blue } ) ` ;
break ;
case "hex" :
return rgbToHex ( color ) ;
break ;
case "json" :
return color ;
break ;
default :
throw new Error ( "outputType must be \"rgb\", \"hex\", \"json\"" ) ;
} ;
} / * else if ( color . startsWith ( "hsl" ) ) {
throw new Error ( "HSL is not implemented yet" ) ;
} * / e l s e {
throw new Error ( 'Color must be of the type "rgb(red,green,blue)"' /* or "hsl(hue,saturation%,luminance%)"*/ ) ;
} ;
} ;
} else if ( typeof ( color ) === "object" ) {
2022-12-08 11:03:20 -05:00
if ( typeof ( color . r ) === "undefined" || typeof ( color . g ) === "undefined" || typeof ( color . b ) === "undefined" ) {
2022-11-12 13:47:48 -05:00
throw new Error ( "Color must be of the form {r: red, g: green, b: blue}" ) ;
} ;
//console.log("received color: " + JSON.stringify(color));
var red = color . r + offset ;
var green = color . g + offset ;
var blue = color . b + offset ;
//console.log(`altered color: rgb(${red},${green},${blue})`);
//rounding and bounding
red = Math . round ( red ) ;
green = Math . round ( green ) ;
blue = Math . round ( blue ) ;
//console.log(`rounded color: rgb(${red},${green},${blue})`);
red = bound ( red , 0 , 255 )
green = bound ( green , 0 , 255 )
blue = bound ( blue , 0 , 255 )
//console.log(`bounded color: rgb(${red},${green},${blue})`);
color = { r : red , g : green , b : blue } ;
switch ( outputType . toLowerCase ( ) ) {
case "rgb" :
return ` rgb( ${ red } , ${ green } , ${ blue } ) ` ;
break ;
case "hex" :
return rgbToHex ( color ) ;
break ;
case "json" :
return color ;
break ;
default :
throw new Error ( "outputType must be \"rgb\", \"hex\", \"json\"" ) ;
} ;
} ;
} ;
function rgbObjectToString ( color ) {
if ( typeof ( color ) !== "object" ) {
throw new Error ( "Input color is not an object" ) ;
} ;
var red = color . r ;
var green = color . g ;
var blue = color . b ;
//console.log(`Colors loaded (${red}, ${green}, ${blue})`);
red = Math . round ( red ) ;
green = Math . round ( green ) ;
blue = Math . round ( blue ) ;
//console.log(`Colors rounded to (${red}, ${green}, ${blue})`);
red = bound ( red , 0 , 255 )
green = bound ( green , 0 , 255 )
blue = bound ( blue , 0 , 255 )
//console.log(`Colors bounded to (${red}, ${green}, ${blue})`);
return ` rgb( ${ red } , ${ green } , ${ blue } ) `
} ;
2022-11-13 11:37:30 -05:00
function convertColorFormats ( color , outputType = "rgb" ) { //Hex triplet and object to rgb(), while rgb() is untouched
2022-11-12 13:47:48 -05:00
if ( typeof ( color ) === "undefined" ) {
//console.log("Warning: An element has an undefined color. Unfortunately, due to how the code is structured, I can't say which one.");
2022-11-13 11:37:30 -05:00
//color = "#FF00FF";
2022-11-13 18:11:19 -05:00
throw new Error ( "Color is undefined!" ) ;
2022-11-12 13:47:48 -05:00
} ;
2022-11-13 11:37:30 -05:00
//console.log("Logged color for convertColorFormats: " + color);
if ( typeof ( color ) === "string" ) {
if ( typeof ( color ) === "string" && color . length < 10 ) {
//console.log(`detected as hex: ${color}`);
//catch missing octothorpes
if ( ! color . startsWith ( "#" ) ) {
color = "#" + color ;
} ;
//console.log(`octothorpe checked: ${color}`);
color = hexToRGB ( color ) ;
if ( color === null ) {
throw new Error ( "hexToRGB(color) was null (maybe it's an invalid hex triplet?)" ) ;
} ;
switch ( outputType . toLowerCase ( ) ) {
case "rgb" :
return ` rgb( ${ color . r } , ${ color . g } , ${ color . b } ) ` ;
break ;
case "hex" :
return rgbToHex ( color ) ;
break ;
case "json" :
return color ;
break ;
2022-11-13 18:11:19 -05:00
case "array" :
return [ color . r , color . g , color . b ] ;
break ;
2022-11-13 11:37:30 -05:00
default :
2022-11-13 18:11:19 -05:00
throw new Error ( "outputType must be \"rgb\", \"hex\", \"json\", or \"array\"" ) ;
2022-11-13 11:37:30 -05:00
} ;
} else {
if ( typeof ( color ) === "string" && color . startsWith ( "rgb(" ) ) {
//console.log(`convertColorFormats: calling rgbStringToObject on color ${color}`);
color = rgbStringToObject ( color , true , false ) ;
switch ( outputType . toLowerCase ( ) ) {
case "rgb" :
if ( typeof ( color ) === "string" ) { color = rgbStringToObject ( color ) } ;
return ` rgb( ${ color . r } , ${ color . g } , ${ color . b } ) ` ;
break ;
case "hex" :
return rgbToHex ( color ) ;
break ;
case "json" :
return color ;
break ;
2022-11-13 18:11:19 -05:00
case "array" :
return [ color . r , color . g , color . b ] ;
break ;
2022-11-13 11:37:30 -05:00
default :
2022-11-13 18:11:19 -05:00
throw new Error ( "outputType must be \"rgb\", \"hex\", \"json\", or \"array\"" ) ;
2022-11-13 11:37:30 -05:00
} ;
} else {
throw new Error ( 'Color must be of the type "rgb(red,green,blue)"' ) ;
} ;
} ;
} else if ( typeof ( color ) === "object" ) {
switch ( outputType . toLowerCase ( ) ) {
case "rgb" :
return ` rgb( ${ color . r } , ${ color . g } , ${ color . b } ) ` ;
break ;
case "hex" :
return rgbToHex ( color ) ;
break ;
case "json" :
return color ;
break ;
2022-11-13 18:11:19 -05:00
case "array" :
return [ color . r , color . g , color . b ] ;
break ;
2022-11-13 11:37:30 -05:00
default :
2022-11-13 18:11:19 -05:00
throw new Error ( "outputType must be \"rgb\", \"hex\", \"json\", or \"array\"" ) ;
2022-11-12 13:47:48 -05:00
} ;
} ;
} ;
2022-11-13 11:37:30 -05:00
function rgbHexCatcher ( color ) {
return convertColorFormats ( color , "rgb" ) ;
} ;
2022-11-12 13:47:48 -05:00
2023-01-24 13:39:51 -05:00
function averageColorObjects ( color1 , color2 , weight1 = 0.5 ) { //misnomer, actually a linear interpolation but it's too late to rename that
//third argument is for color1 and expects a float from 0 to 1, where 0 means "all color2" and 1 means "all color1"
//(backwards from how it should work)
2022-11-12 13:47:48 -05:00
var w1 = Math . min ( Math . max ( weight1 , 0 ) , 1 )
var red1 = color1 . r
var green1 = color1 . g
var blue1 = color1 . b
var red2 = color2 . r
var green2 = color2 . g
var blue2 = color2 . b
var red3 = ( red1 * w1 ) + ( red2 * ( 1 - w1 ) )
var green3 = ( green1 * w1 ) + ( green2 * ( 1 - w1 ) )
var blue3 = ( blue1 * w1 ) + ( blue2 * ( 1 - w1 ) )
return { r : red3 , g : green3 , b : blue3 }
} ;
2023-01-24 13:39:51 -05:00
function lerpColors ( color1 , color2 , outputType = "rgb" , weight1 = 0.5 ) {
color1 = convertColorFormats ( color1 , "json" ) ;
color2 = convertColorFormats ( color2 , "json" ) ;
theColor = averageColorObjects ( color1 , color2 , weight1 ) ;
return convertColorFormats ( theColor , outputType ) ;
} ;
2022-11-12 15:08:56 -05:00
function multiplyColors ( color1 , color2 , outputType = "rgb" ) {
//normalize rgb()/hex by turning any hex into rgb() and then rgb()s to {r,g,b}
if ( typeof ( color1 ) !== "object" ) {
2022-11-13 11:37:30 -05:00
color1 = convertColorFormats ( color1 , "json" ) ;
2022-11-12 15:08:56 -05:00
} ;
if ( typeof ( color2 ) !== "object" ) {
2022-11-13 11:37:30 -05:00
color2 = convertColorFormats ( color2 , "json" ) ;
2022-11-12 15:08:56 -05:00
} ;
var finalR = Math . round ( color1 . r * ( color2 . r / 255 ) ) ;
var finalG = Math . round ( color1 . g * ( color2 . g / 255 ) ) ;
var finalB = Math . round ( color1 . b * ( color2 . b / 255 ) ) ;
var finalColor = { r : finalR , g : finalG , b : finalB } ;
switch ( outputType . toLowerCase ( ) ) {
case "rgb" :
return ` rgb( ${ finalColor . r } , ${ finalColor . g } , ${ finalColor . b } ) ` ;
break ;
case "hex" :
return rgbToHex ( finalColor ) ;
break ;
case "json" :
return finalColor ;
break ;
default :
throw new Error ( "outputType must be \"rgb\", \"hex\", \"json\"" ) ;
} ;
} ;
function divideColors ( color1 , color2 , outputType = "rgb" ) { //color2 is the divisor and color1 the dividend (base/original color)
//normalize rgb()/hex by turning any hex into rgb() and then rgb()s to {r,g,b}
if ( typeof ( color1 ) !== "object" ) {
2022-11-13 11:37:30 -05:00
color1 = convertColorFormats ( color1 , "json" ) ;
2022-11-12 15:08:56 -05:00
} ;
if ( typeof ( color2 ) !== "object" ) {
2022-11-13 11:37:30 -05:00
color2 = convertColorFormats ( color2 , "json" ) ;
2022-11-12 15:08:56 -05:00
} ;
var finalR = bound ( Math . round ( 255 / ( color2 . r / color1 . r ) ) , 0 , 255 ) ;
var finalG = bound ( Math . round ( 255 / ( color2 . g / color1 . g ) ) , 0 , 255 ) ;
var finalB = bound ( Math . round ( 255 / ( color2 . b / color1 . b ) ) , 0 , 255 ) ;
if ( isNaN ( finalR ) ) { finalR = 255 } ;
if ( isNaN ( finalG ) ) { finalG = 255 } ;
if ( isNaN ( finalB ) ) { finalB = 255 } ;
var finalColor = { r : finalR , g : finalG , b : finalB } ;
switch ( outputType . toLowerCase ( ) ) {
case "rgb" :
return ` rgb( ${ finalColor . r } , ${ finalColor . g } , ${ finalColor . b } ) ` ;
break ;
case "hex" :
return rgbToHex ( finalColor ) ;
break ;
case "json" :
return finalColor ;
break ;
default :
throw new Error ( "outputType must be \"rgb\", \"hex\", \"json\"" ) ;
} ;
} ;
function addColors ( color1 , color2 , outputType = "rgb" ) {
//normalize rgb()/hex by turning any hex into rgb() and then rgb()s to {r,g,b}
if ( typeof ( color1 ) !== "object" ) {
2022-11-13 11:37:30 -05:00
color1 = convertColorFormats ( color1 , "json" ) ;
2022-11-12 15:08:56 -05:00
} ;
if ( typeof ( color2 ) !== "object" ) {
2022-11-13 11:37:30 -05:00
color2 = convertColorFormats ( color2 , "json" ) ;
} ;
2022-11-12 15:08:56 -05:00
var finalR = bound ( Math . round ( color1 . r + color2 . r ) , 0 , 255 )
2022-11-16 14:28:04 -05:00
var finalG = bound ( Math . round ( color1 . g + color2 . g ) , 0 , 255 )
2022-11-12 15:08:56 -05:00
var finalB = bound ( Math . round ( color1 . b + color2 . b ) , 0 , 255 )
var finalColor = { r : finalR , g : finalG , b : finalB } ;
switch ( outputType . toLowerCase ( ) ) {
case "rgb" :
return ` rgb( ${ finalColor . r } , ${ finalColor . g } , ${ finalColor . b } ) ` ;
break ;
case "hex" :
return rgbToHex ( finalColor ) ;
break ;
case "json" :
return finalColor ;
break ;
default :
throw new Error ( "outputType must be \"rgb\", \"hex\", \"json\"" ) ;
} ;
} ;
function subtractColors ( color1 , color2 , outputType = "rgb" ) {
//normalize rgb()/hex by turning any hex into rgb() and then rgb()s to {r,g,b}
if ( typeof ( color1 ) !== "object" ) {
2022-11-13 11:37:30 -05:00
color1 = convertColorFormats ( color1 , "json" ) ;
2022-11-12 15:08:56 -05:00
} ;
if ( typeof ( color2 ) !== "object" ) {
2022-11-13 11:37:30 -05:00
color2 = convertColorFormats ( color2 , "json" ) ;
} ;
2022-11-12 15:08:56 -05:00
var finalR = bound ( Math . round ( color1 . r - color2 . r ) , 0 , 255 )
2022-11-16 14:28:04 -05:00
var finalG = bound ( Math . round ( color1 . g - color2 . g ) , 0 , 255 )
2022-11-12 15:08:56 -05:00
var finalB = bound ( Math . round ( color1 . b - color2 . b ) , 0 , 255 )
var finalColor = { r : finalR , g : finalG , b : finalB } ;
switch ( outputType . toLowerCase ( ) ) {
case "rgb" :
return ` rgb( ${ finalColor . r } , ${ finalColor . g } , ${ finalColor . b } ) ` ;
break ;
case "hex" :
return rgbToHex ( finalColor ) ;
break ;
case "json" :
return finalColor ;
break ;
default :
throw new Error ( "outputType must be \"rgb\", \"hex\", \"json\"" ) ;
} ;
} ;
2022-11-12 15:37:41 -05:00
function averageRgbPrefixedColorArray ( colorArray , returnObject = false ) { //array of rgb()s to single rgb() of average color
//averageRgbPrefixedColorArray(["rgb(255,0,0)", "rgb(0,0,0)", "rgb(0,0,255)"]);
//console.log("Averaging started");
var reds = [ ] ;
var greens = [ ] ;
var blues = [ ] ;
for ( k = 0 ; k < colorArray . length ; k ++ ) {
//console.log("Average function: Executing catcher on " + colorArray);
2022-11-13 11:37:30 -05:00
var color = convertColorFormats ( colorArray [ k ] ) ;
2022-11-12 15:37:41 -05:00
//console.log("Logged color for aRPCA: " + color);
color = color . split ( "," ) ;
var red = parseFloat ( color [ 0 ] . substring ( 4 ) ) ;
reds . push ( red )
var green = parseFloat ( color [ 1 ] ) ;
greens . push ( green )
var blue = parseFloat ( color [ 2 ] . slice ( 0 , - 1 ) ) ;
blues . push ( blue )
} ;
redAverage = Math . round ( averageNumericArray ( reds ) ) ;
greenAverage = Math . round ( averageNumericArray ( greens ) ) ;
blueAverage = Math . round ( averageNumericArray ( blues ) ) ;
var output ;
returnObject ? output = { r : redAverage , g : greenAverage , b : blueAverage } : output = ` rgb( ${ redAverage } , ${ greenAverage } , ${ blueAverage } ) ` ;
//console.log("Averaging finished, product: " + output);
return output ;
} ;
//https://stackoverflow.com/questions/46432335/hex-to-hsl-convert-javascript
2022-11-13 18:11:19 -05:00
function rgbStringToHSL ( rgb , outputType = "array" ) { //Originally a hex-to-HSL function, edited to take RGB and spit out an array
2022-11-12 15:37:41 -05:00
//console.log("HSLing some RGBs");
var result = rgbStringToUnvalidatedObject ( rgb ) ;
var r = result . r ;
var g = result . g ;
var b = result . b ;
r /= 255 , g /= 255 , b /= 255 ;
var max = Math . max ( r , g , b ) , min = Math . min ( r , g , b ) ;
var h , s , l = ( max + min ) / 2 ;
if ( max == min ) {
h = s = 0 ; // achromatic
} else {
var d = max - min ;
s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min ) ;
switch ( max ) {
case r : h = ( g - b ) / d + ( g < b ? 6 : 0 ) ; break ;
case g : h = ( b - r ) / d + 2 ; break ;
case b : h = ( r - g ) / d + 4 ; break ;
}
h /= 6 ;
} ;
s = s * 100 ;
s = Math . round ( s ) ;
l = l * 100 ;
l = Math . round ( l ) ;
h = Math . round ( 360 * h ) ;
//var colorInHSL = 'hsl(' + h + ', ' + s + '%, ' + l + '%)';
//Edit to return an array
2022-11-13 18:11:19 -05:00
switch ( outputType . toLowerCase ( ) ) {
case "array" :
return [ h , s , l ] ;
break ;
case "hsl" :
return ` hsl( ${ h } , ${ s } %, ${ l } %) ` ;
break ;
case "json" :
return { h : h , s : s , l : l } ;
default :
throw new Error ( "outputType must be \"array\", \"hsl\", or \"json\"" ) ;
break ;
} ;
2022-11-12 15:37:41 -05:00
//console.log("HSL output "+ colorInHSL + ".");
2022-11-13 18:11:19 -05:00
} ;
function normalizeColorToHslObject ( color , arrayType = null ) {
var ambiguousArrayError = "changeSaturation can't tell if the array input is supposed to be RGB or HSL. Please use an \"arrayType\" argument of \"rgb\" or \"hsl\"." ;
var isHsl = false ;
if ( Array . isArray ( color ) ) {
if ( arrayType === null ) {
throw new Error ( ambiguousArrayError ) ;
} else if ( arrayType === "rgb" ) {
color = ` rgb( ${ color [ 0 ] } , ${ color [ 1 ] } , ${ color [ 2 ] } ) ` ;
color = rgbStringToHSL ( color , "json" ) ; //rgb arr to hsl obj
} else if ( arrayType === "hsl" ) {
color = { h : color [ 0 ] , s : color [ 1 ] , l : color [ 2 ] } ; //hsl arr to hsl obj
} else {
throw new Error ( ambiguousArrayError ) ;
} ;
} else {
//by this point, any array cases would have been handled, leaving just hex (rgb), json rgb, json hsl, string rgb, and string hsl
if ( typeof ( color ) === "string" ) {
if ( color . length < 10 ) { //detect hex: assume hex triplet if too short to be a well-formed rgb()
if ( ! color . startsWith ( "#" ) ) {
color = "#" + color ; //catch missing #
} ;
isHsl = false ;
} ;
if ( color . startsWith ( "rgb(" ) ) { //detect rgb(): self-explanatory
isHsl = false ;
} ;
if ( color . startsWith ( "hsl(" ) ) { //detect hsl(): self-explanatory
isHsl = true ;
} ;
} else if ( typeof ( color ) === "object" ) {
if ( typeof ( color . r ) !== "undefined" ) { //detect {r,g,b}: check for r key
isHsl = false ;
} ;
if ( typeof ( color . h ) !== "undefined" ) { //detect {h,s,l}: check for h key
isHsl = true ;
} ;
} ;
if ( ! isHsl ) {
color = convertColorFormats ( color , "rgb" ) ; //make any RGBs rgb()
color = rgbStringToHSL ( color , "json" ) ; //make that rgb() an {h,s,l}
} else { //by this point, it would have to either be a string or an object
if ( typeof ( color ) === "string" ) { //if it's a string
color = hslColorStringToObject ( color ) //now it's an object
} ;
} ;
} ;
return color ;
} ;
function convertHslObjects ( color , outputType = "rgb" ) {
switch ( outputType . toLowerCase ( ) ) {
//RGB cases
case "rgb" :
color = hexToRGB ( hslToHex ( ... Object . values ( color ) ) ) ; //hsl to hex, hex to rgb_json, and rgb_json to rgb()
return ` rgb( ${ color . r } , ${ color . g } , ${ color . b } ) ` ;
break ;
case "hex" :
color = hslToHex ( ... Object . values ( color ) ) ; //hsl to hex
return color ;
break ;
case "rgbjson" :
case "rgb-json" :
case "rgb_json" :
color = hexToRGB ( hslToHex ( ... Object . values ( color ) ) ) ; //hsl to hex and hex to rgb_json
return color ;
break ;
case "rgbarray" :
case "rgb-array" :
case "rgb_array" :
color = hexToRGB ( hslToHex ( ... Object . values ( color ) ) ) ; //hsl to hex, hex to rgb_json, and rgb_json to rgb_array
return [ color . r , color . g , color . b ] ;
break ;
//HSL cases
case "hsl" :
//note: color was previously converted to {h, s, l}
return ` hsl( ${ color . h } , ${ color . s } %, ${ color . l } %) ` ;
break ;
case "hsljson" :
case "hsl-json" :
case "hsl_json" :
return color ;
break ;
case "hslarray" :
case "hsl-array" :
case "hsl_array" :
return [ color . h , color . s , color . l ] ;
break ;
default :
throw new Error ( "outputType must be \"rgb\", \"hex\", \"rgb_json\", \"rgb_array\", \"hsl\", \"hsl_json\", or \"hsl_array\"" ) ;
} ;
}
function changeSaturation ( color , saturationChange , operationType = "add" , outputType = "rgb" , arrayType = null ) {
color = normalizeColorToHslObject ( color , arrayType ) ;
//only {h,s,l} should exist now
//Math
switch ( operationType . toLowerCase ( ) ) {
case "+" :
case "add" :
color . s += saturationChange ;
break ;
case "-" :
case "subtract" :
color . s -= saturationChange ;
break ;
case "*" :
2022-11-14 11:21:15 -05:00
case "x" :
case "× " :
2022-11-13 18:11:19 -05:00
case "multiply" :
color . s *= saturationChange ;
break ;
case "/" :
2022-11-14 11:21:15 -05:00
case "÷" :
2022-11-13 18:11:19 -05:00
case "divide" :
color . s /= saturationChange ;
break ;
2022-11-14 11:21:15 -05:00
case "=" :
case "set" :
color . s = saturationChange ;
break ;
2022-11-22 20:15:48 -05:00
case ">" :
case ">=" :
case "min" : //lower-bounds the color
color . s = Math . max ( color . s , saturationChange ) ; //math.max to bound it to the higher of the input number or the existing color
break ;
case "<" :
case "<=" :
case "max" : //upper-bounds the color
color . s = Math . min ( color . s , saturationChange ) ; //math.min to bound it to the lower of the input number or the existing color
break ;
2022-11-13 18:11:19 -05:00
default :
2022-11-22 20:15:48 -05:00
throw new Error ( "Operation must be \"add\", \"subtract\", \"multiply\", \"divide\", \"set\", \"min\", or \"max\"" ) ;
2022-11-13 18:11:19 -05:00
} ;
color . h = Math . round ( color . h % 360 ) ;
color . s = Math . round ( bound ( color . s , 0 , 100 ) ) ;
color . l = Math . round ( bound ( color . l , 0 , 100 ) ) ;
return convertHslObjects ( color , outputType )
} ;
function changeLuminance ( color , luminanceChange , operationType = "add" , outputType = "rgb" , arrayType = null ) {
color = normalizeColorToHslObject ( color , arrayType ) ;
//only {h,s,l} should exist now
//Math
switch ( operationType . toLowerCase ( ) ) {
case "+" :
case "add" :
2022-11-14 11:21:15 -05:00
color . l += luminanceChange ;
break ;
case "-" :
case "subtract" :
color . l -= luminanceChange ;
break ;
case "*" :
case "x" :
case "× " :
case "multiply" :
color . l *= luminanceChange ;
break ;
case "/" :
case "÷" :
case "divide" :
color . l /= luminanceChange ;
break ;
case "=" :
case "set" :
color . l = luminanceChange ;
break ;
2022-11-22 20:15:48 -05:00
case ">" :
case ">=" :
case "min" :
color . l = Math . max ( color . l , luminanceChange ) ;
break ;
case "<" :
case "<=" :
case "max" :
color . l = Math . min ( color . l , luminanceChange ) ;
break ;
2022-11-14 11:21:15 -05:00
default :
2022-11-22 20:15:48 -05:00
throw new Error ( "Operation must be \"add\", \"subtract\", \"multiply\", \"divide\", \"set\", \"min\", or \"max\"" ) ;
2022-11-14 11:21:15 -05:00
} ;
color . h = Math . round ( color . h % 360 ) ;
color . s = Math . round ( bound ( color . s , 0 , 100 ) ) ;
color . l = Math . round ( bound ( color . l , 0 , 100 ) ) ;
return convertHslObjects ( color , outputType ) ;
} ;
function changeHue ( color , hueChange , operationType = "add" , outputType = "rgb" , arrayType = null ) {
color = normalizeColorToHslObject ( color , arrayType ) ;
//only {h,s,l} should exist now
//Math
switch ( operationType . toLowerCase ( ) ) {
case "+" :
case "add" :
color . h += hueChange ;
2022-11-13 18:11:19 -05:00
break ;
case "-" :
case "subtract" :
2022-11-14 11:21:15 -05:00
color . h -= hueChange ;
2022-11-13 18:11:19 -05:00
break ;
case "*" :
2022-11-14 11:21:15 -05:00
case "x" :
case "× " :
2022-11-13 18:11:19 -05:00
case "multiply" :
2022-11-14 11:21:15 -05:00
color . h *= hueChange ;
2022-11-13 18:11:19 -05:00
break ;
case "/" :
2022-11-14 11:21:15 -05:00
case "÷" :
2022-11-13 18:11:19 -05:00
case "divide" :
2022-11-14 11:21:15 -05:00
color . h /= hueChange ;
break ;
case "=" :
case "set" :
color . h = hueChange ;
2022-11-13 18:11:19 -05:00
break ;
2022-11-22 20:15:48 -05:00
case ">" :
case ">=" :
case "min" :
color . h = Math . max ( color . h , hueChange ) ;
break ;
case "<" :
case "<=" :
case "max" :
color . h = Math . min ( color . h , hueChange ) ;
break ;
2022-11-13 18:11:19 -05:00
default :
2022-11-22 20:15:48 -05:00
throw new Error ( "Operation must be \"add\", \"subtract\", \"multiply\", \"divide\", \"set\", \"min\", or \"max\"" ) ;
2022-11-13 18:11:19 -05:00
} ;
color . h = Math . round ( color . h % 360 ) ;
color . s = Math . round ( bound ( color . s , 0 , 100 ) ) ;
color . l = Math . round ( bound ( color . l , 0 , 100 ) ) ;
return convertHslObjects ( color , outputType ) ;
} ;
2023-01-23 17:14:59 -05:00
function colorToHsl ( color , outputType = "rgb" ) {
2022-11-13 18:11:19 -05:00
color = convertColorFormats ( color , "rgb" ) ;
2023-01-23 17:14:59 -05:00
color = rgbStringToHSL ( color , outputType ) ;
return color ;
2022-11-12 15:37:41 -05:00
} ;
//https://stackoverflow.com/questions/36721830/convert-hsl-to-rgb-and-hex
function hslToHex ( h , s , l ) { //h, s, l params to hex triplet
//console.log(`Hexing some HSLs (the HSLs are ${h},${s},${l})`)
2022-11-13 18:11:19 -05:00
s = bound ( s , 0 , 100 ) ; //limit to 0-100
l = bound ( l , 0 , 100 ) ;
2022-11-12 15:37:41 -05:00
l /= 100 ;
var a = s * Math . min ( l , 1 - l ) / 100 ;
var f = n => {
var k = ( n + h / 30 ) % 12 ;
var color = l - a * Math . max ( Math . min ( k - 3 , 9 - k , 1 ) , - 1 ) ;
return Math . round ( 255 * color ) . toString ( 16 ) . padStart ( 2 , '0' ) ; // convert to Hex and prefix "0" if needed
} ;
//console.log(`Hexed to #${f(0)}${f(8)}${f(4)}`)
return ` # ${ f ( 0 ) } ${ f ( 8 ) } ${ f ( 4 ) } ` ;
} ;
//Pixels
2022-11-12 13:47:48 -05:00
function exposedToAir ( pixel ) {
return ( isEmpty ( pixel . x + 1 , pixel . y ) || isEmpty ( pixel . x - 1 , pixel . y ) || isEmpty ( pixel . x , pixel . y + 1 ) || isEmpty ( pixel . x , pixel . y - 1 ) ) ;
} ;
function tryTarnish ( pixel , element , chance ) {
if ( exposedToAir ( pixel ) ) {
if ( Array . isArray ( element ) ) {
if ( Math . random ( ) < chance ) {
changePixel ( pixel , randomChoice ( element ) ) ;
} ;
} else {
if ( Math . random ( ) < chance ) {
changePixel ( pixel , element ) ;
} ;
} ;
} ;
} ;
//Try to create a pixel, return true if it could be created and false if it couldn't
function tryCreatePixel ( elementInput , x , y ) {
//array handling
if ( elementInput . includes ( "," ) ) { //CSTA
elementInput = elementInput . split ( "," ) ;
} ;
if ( Array . isArray ( elementInput ) ) { //if element list
elementInput = elementInput . filter ( function ( e ) {
return elementExists ( e ) ;
} ) ;
if ( elementInput . length === 0 ) { throw new Error ( "elementInput has no existing elements" ) } ;
elementInput = randomChoice ( elementInput ) ;
} ;
//existence check
if ( ! elementExists ( elementInput ) ) {
2022-11-12 15:52:34 -05:00
throw new Error ( "Element " + elementInput + " doesn't exist!" ) ;
2022-11-12 13:47:48 -05:00
} ;
//actual creation check
if ( isEmpty ( x , y ) ) {
2022-11-12 15:52:34 -05:00
createPixel ( elementInput , x , y ) ;
2022-11-12 13:47:48 -05:00
return true ;
} else {
return false ;
} ;
} ;
2023-02-21 12:39:09 -05:00
function createPixelReturn ( element , x , y ) { //sugar
var newPixel = new Pixel ( x , y , element ) ;
currentPixels . push ( newPixel ) ;
checkUnlock ( element ) ;
return newPixel ;
} ;
function changePixelReturn ( pixel , element , changetemp = true ) {
pixel . element = element ;
pixel . color = pixelColorPick ( pixel ) ;
pixel . start = pixelTicks ;
var elementInfo = elements [ element ] ;
if ( elementInfo . burning == true ) {
pixel . burning = true ;
pixel . burnStart = pixelTicks ;
}
else if ( pixel . burning && ! elementInfo . burn ) {
delete pixel . burning ;
delete pixel . burnStart ;
}
delete pixel . origColor ; // remove stain
if ( pixel . r && ! elementInfo . rotatable ) {
delete pixel . r ;
}
if ( pixel . flipX && ! elementInfo . flippableX ) {
delete pixel . flipX ;
}
if ( pixel . flipY && ! elementInfo . flippableY ) {
delete pixel . flipY ;
}
// If elementInfo.flippableX, set it to true or false randomly
if ( elementInfo . flipX !== undefined ) { pixel . flipX = elementInfo . flipX }
else if ( elementInfo . flippableX ) {
pixel . flipX = Math . random ( ) >= 0.5 ;
}
// If elementInfo.flippableY, set it to true or false randomly
if ( elementInfo . flipY !== undefined ) { pixel . flipY = elementInfo . flipY }
else if ( elementInfo . flippableY ) {
pixel . flipY = Math . random ( ) >= 0.5 ;
}
if ( elementInfo . temp != undefined && changetemp ) {
pixel . temp = elementInfo . temp ;
pixelTempCheck ( pixel )
}
// If elementInfo.properties, set each key to its value
if ( elementInfo . properties !== undefined ) {
for ( var key in elementInfo . properties ) {
// If it is an array or object, make a copy of it
if ( typeof elementInfo . properties [ key ] == "object" ) {
pixel [ key ] = JSON . parse ( JSON . stringify ( elementInfo . properties [ key ] ) ) ;
}
else {
pixel [ key ] = elementInfo . properties [ key ] ;
}
}
}
checkUnlock ( element ) ;
return pixel ;
} ;
2023-02-19 10:45:31 -05:00
function storeFirstTouchingElement ( pixel , propertyName , copyTemp = true , spread = true ) {
var info = elements [ pixel . element ] ;
if ( pixel [ propertyName ] ) {
return false ;
} ;
for ( i = 0 ; i < adjacentCoords . length ; i ++ ) {
var newCoords = { x : pixel . x + adjacentCoords [ i ] [ 0 ] , y : pixel . y + adjacentCoords [ i ] [ 1 ] } ;
if ( ! isEmpty ( newCoords . x , newCoords . y , true ) ) {
newPixel = pixelMap [ newCoords . x ] [ newCoords . y ] ;
if ( info . ignore && info . ignore . indexOf ( newPixel . element ) !== - 1 ) {
continue ;
} ;
if ( newPixel . element != pixel . element && newPixel . element != "wire" ) {
pixel [ propertyName ] = newPixel . element ;
if ( copyTemp ) { pixel . temp = newPixel . temp } ;
return newPixel . element ;
}
else if ( newPixel [ propertyName ] && spread ) {
pixel [ propertyName ] = newPixel [ propertyName ] ;
pixel . temp = newPixel . temp ;
return newPixel [ propertyName ] ;
}
}
} ;
} ;
2022-11-12 13:47:48 -05:00
function breakPixel ( pixel , changetemp = false ) {
var info = elements [ pixel . element ] ;
if ( typeof ( info . breakInto ) === "undefined" ) {
return false ;
} ;
var breakIntoElement = info . breakInto ;
if ( Array . isArray ( breakIntoElement ) ) {
breakIntoElement = breakIntoElement [ Math . floor ( Math . random ( ) * breakIntoElement . length ) ]
} ;
changePixel ( pixel , breakIntoElement , changetemp )
} ;
2022-11-12 15:37:41 -05:00
function tryBreak ( pixel , changetemp = false , defaultBreakIntoDust = false ) {
var info = elements [ pixel . element ] ;
var hardness = defaultHardness ;
if ( typeof ( info . hardness ) === "number" ) {
hardness = info . hardness ;
} ;
hardness = 1 - hardness ; //invert hardness, so a hardness of 0 becomes a 100% chance and a hardness of 1 becomes a 0% chance
if ( Math . random ( ) < hardness ) {
return breakPixel ( pixel , changetemp = false , defaultBreakIntoDust = false ) ;
} else {
return false ;
} ;
} ;
2022-12-18 13:37:21 -05:00
function reactionStealer ( pixel , newPixel , reactionTarget ) {
if ( ! elements [ reactionTarget ] ) {
throw new Error ( ` No such element ${ reactionTarget } ! ` ) ;
} ;
if ( typeof ( newPixel ) === "undefined" ) { //timing issue?
return false ;
} ;
var newElement = newPixel . element ;
var newInfo = elements [ newElement ] ;
if ( typeof ( newInfo . reactions ) === "undefined" ) {
return false ;
} ;
if ( typeof ( newInfo . reactions [ reactionTarget ] ) === "undefined" ) {
return false ;
} ;
var pixel2 = pixel ;
var pixel1 = newPixel ;
var r = newInfo . reactions [ reactionTarget ] ;
if ( r . setting && settings [ r . setting ] === 0 ) {
return false ;
}
// r has the attribute "y" which is a range between two y values
// r.y example: [10,30]
// return false if y is defined and pixel1's y is not in the range
if ( r . tempMin !== undefined && pixel1 . temp < r . tempMin ) {
return false ;
}
if ( r . tempMax !== undefined && pixel1 . temp > r . tempMax ) {
return false ;
}
if ( r . charged && ! pixel . charge ) {
return false ;
}
if ( r . chance !== undefined && Math . random ( ) > r . chance ) {
return false ;
}
if ( r . y !== undefined && ( pixel1 . y < r . y [ 0 ] || pixel1 . y > r . y [ 1 ] ) ) {
return false ;
}
if ( r . elem1 !== undefined ) {
// if r.elem1 is an array, set elem1 to a random element from the array, otherwise set it to r.elem1
if ( Array . isArray ( r . elem1 ) ) {
var elem1 = r . elem1 [ Math . floor ( Math . random ( ) * r . elem1 . length ) ] ;
} else { var elem1 = r . elem1 ; }
if ( elem1 == null ) {
deletePixel ( pixel1 . x , pixel1 . y ) ;
}
else {
changePixel ( pixel1 , elem1 ) ;
}
}
if ( r . charge1 ) { pixel1 . charge = r . charge1 ; }
if ( r . temp1 ) { pixel1 . temp += r . temp1 ; pixelTempCheck ( pixel1 ) ; }
if ( r . color1 ) { // if it's a list, use a random color from the list, else use the color1 attribute
pixel1 . color = pixelColorPick ( pixel1 , Array . isArray ( r . color1 ) ? r . color1 [ Math . floor ( Math . random ( ) * r . color1 . length ) ] : r . color1 ) ;
}
if ( r . attr1 ) { // add each attribute to pixel1
for ( var key in r . attr1 ) {
pixel1 [ key ] = r . attr1 [ key ] ;
}
}
if ( r . elem2 !== undefined ) {
// if r.elem2 is an array, set elem2 to a random element from the array, otherwise set it to r.elem2
if ( Array . isArray ( r . elem2 ) ) {
var elem2 = r . elem2 [ Math . floor ( Math . random ( ) * r . elem2 . length ) ] ;
} else { var elem2 = r . elem2 ; }
if ( elem2 == null ) {
deletePixel ( pixel2 . x , pixel2 . y ) ;
}
else {
changePixel ( pixel2 , elem2 ) ;
}
}
if ( r . charge2 ) { pixel2 . charge = r . charge2 ; }
if ( r . temp2 ) { pixel2 . temp += r . temp2 ; pixelTempCheck ( pixel2 ) ; }
if ( r . color2 ) { // if it's a list, use a random color from the list, else use the color2 attribute
pixel2 . color = pixelColorPick ( pixel2 , Array . isArray ( r . color2 ) ? r . color2 [ Math . floor ( Math . random ( ) * r . color2 . length ) ] : r . color2 ) ;
}
if ( r . attr2 ) { // add each attribute to pixel2
for ( var key in r . attr2 ) {
pixel2 [ key ] = r . attr2 [ key ] ;
}
}
if ( r . func ) { r . func ( pixel1 , pixel2 ) ; }
return r . elem1 !== undefined || r . elem2 !== undefined ;
} ;
2022-12-08 11:03:20 -05:00
//World
function breakCircle ( x , y , radius , respectHardness = false , changeTemp = false , defaultBreakIntoDust = false ) {
var coords = circleCoords ( x , y , radius ) ;
for ( i = 0 ; i < coords . length ; i ++ ) {
coordX = coords [ i ] . x ;
coordY = coords [ i ] . y ;
if ( ! isEmpty ( coordX , coordY , true ) ) {
var pixel = pixelMap [ coordX ] [ coordY ] ;
respectHardness ? tryBreak ( pixel , changeTemp , defaultBreakIntoDust ) : breakPixel ( pixel , changeTemp , defaultBreakIntoDust ) ;
} ;
} ;
} ;
function fillCircle ( element , x , y , radius , overwrite = false ) {
var coords = circleCoords ( x , y , radius ) ;
var newElement = element ;
if ( Array . isArray ( newElement ) ) {
newElement = newElement [ Math . floor ( Math . random ( ) * newElement . length ) ] ;
} ;
for ( i = 0 ; i < coords . length ; i ++ ) {
2023-02-21 12:39:09 -05:00
coordX = Math . round ( coords [ i ] . x ) ;
coordY = Math . round ( coords [ i ] . y ) ;
2022-12-08 11:03:20 -05:00
if ( overwrite && ! isEmpty ( coordX , coordY , true ) ) {
changePixel ( pixelMap [ coordX ] [ coordY ] , element ) ;
} ;
if ( isEmpty ( coordX , coordY , false ) ) {
createPixel ( element , coordX , coordY ) ;
} ;
} ;
} ;
2023-02-21 12:39:09 -05:00
function fillCircleReturn ( element , x , y , radius , overwrite = false ) {
2023-02-22 16:26:53 -05:00
//console.log("fcr");
2023-02-21 12:39:09 -05:00
var pixels = [ ] ;
2023-02-22 16:26:53 -05:00
//console.log("pixels initted");
2023-02-21 12:39:09 -05:00
var coords = circleCoords ( x , y , radius ) ;
2023-02-22 16:26:53 -05:00
//console.log("coords gotten");
2023-02-21 12:39:09 -05:00
var newElement = element ;
2023-02-22 16:26:53 -05:00
//console.log("element processing");
2023-02-21 12:39:09 -05:00
if ( Array . isArray ( newElement ) ) {
newElement = newElement [ Math . floor ( Math . random ( ) * newElement . length ) ] ;
} ;
2023-02-22 16:26:53 -05:00
//console.log("element processed");
2023-02-21 12:39:09 -05:00
for ( i = 0 ; i < coords . length ; i ++ ) {
2023-02-22 16:26:53 -05:00
//console.log("iterator through spots: " + i);
2023-02-21 12:39:09 -05:00
coordX = Math . round ( coords [ i ] . x ) ;
coordY = Math . round ( coords [ i ] . y ) ;
2023-02-22 16:26:53 -05:00
//console.log(`coord: (${coords[i].x},${coords[i].y})`);
2023-02-21 12:39:09 -05:00
if ( overwrite && ! isEmpty ( coordX , coordY , true ) ) {
2023-02-22 16:26:53 -05:00
//console.log("replaced pixel");
pixels . push ( changePixelReturn ( pixelMap [ coordX ] [ coordY ] , newElement ) ) ;
2023-02-21 12:39:09 -05:00
} ;
if ( isEmpty ( coordX , coordY , false ) ) {
2023-02-22 16:26:53 -05:00
//console.log("created pixel");
pixels . push ( createPixelReturn ( newElement , coordX , coordY ) ) ;
2023-02-21 12:39:09 -05:00
} ;
} ;
2023-02-22 16:26:53 -05:00
//console.log("fcr finished");
//console.log(pixels.map(x => x.element));
2023-02-21 12:39:09 -05:00
return pixels ;
} ;
2023-01-23 17:14:59 -05:00
function isOpenAndOnSurface ( x , y , includeBottomBound = true ) {
if ( ! isEmpty ( x , y , false ) ) {
return false ;
} ;
if ( y + 1 == height ) {
return includeBottomBound ;
} ;
return ! isEmpty ( x , y + 1 , true ) ;
} ;
2022-11-12 13:47:48 -05:00
//Logic
function xor ( c1 , c2 ) {
if ( ! ! c1 && ! c2 ) {
return true ;
} else if ( ! c1 && ! ! c2 ) {
return true ;
} else {
return false ;
} ;
} ;
2022-12-18 13:37:21 -05:00
//currentPixels operations
function findInCurrentPixels ( x , y ) {
var pixel = currentPixels . filter ( function ( pixelObject ) {
return pixelObject . x == x && pixelObject . y == y ;
} ) ;
if ( pixel . length <= 0 ) {
return undefined ;
} ;
if ( pixel . length > 1 ) {
pixel . length = 1 ;
} ;
pixel = pixel [ 0 ] ;
return pixel ;
} ;
function filterCurrentPixels ( filterFunction ) {
return currentPixels . filter ( filterFunction ) ;
} ;
//Filter test functions
function _filterTest _xIsTwenty ( pixel ) {
return pixel . x == 20 ;
} ;
function _filterTest _tempIsOdd ( pixel ) {
return Math . trunc ( pixel . temp ) % 2 == 1 ;
} ;
function _filterTest _redRock ( pixel ) {
if ( typeof ( convertColorFormats ) === "undefined" ) {
throw new Error ( "code_library.js is required!" ) ;
} ;
var color = rgbStringToHSL ( convertColorFormats ( pixel . color , "rgb" ) , "json" ) ;
var isRed = ( ( color . h % 360 ) >= 350 ) || ( ( color . h % 360 ) <= 10 ) ;
var isVivid = ( color . s > 30 ) ;
var isBright = ( color . l > 20 ) ;
return isRed && isVivid && isBright ;
} ;