├── .gitignore ├── examples ├── dino │ ├── assets │ │ ├── bg1.png │ │ ├── bg2.png │ │ ├── gr3.png │ │ ├── bgsky.png │ │ ├── dino.png │ │ ├── dino2.png │ │ └── spike.png │ └── dino.js ├── nes │ ├── roms │ │ └── Tetris.nes │ └── index.js ├── tank │ ├── assets │ │ ├── bg.png │ │ ├── box.png │ │ ├── brick.png │ │ ├── home.png │ │ ├── tank.png │ │ ├── tank2.png │ │ ├── cement.png │ │ ├── bricksheet.png │ │ ├── magicbrick.png │ │ └── tank2 - Copy.png │ └── tank.js └── TicTacToe │ ├── assets │ ├── o2.png │ └── x22.png │ └── ttt.js ├── core ├── config.js ├── input │ └── input.js ├── utils │ └── utils.js ├── animation │ └── animation.js ├── physics │ └── physics.js └── renderer │ └── renderer.js ├── soc_client.js ├── soc.js ├── package.json ├── README.md ├── testPackage.js ├── index.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bak -------------------------------------------------------------------------------- /examples/dino/assets/bg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/dino/assets/bg1.png -------------------------------------------------------------------------------- /examples/dino/assets/bg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/dino/assets/bg2.png -------------------------------------------------------------------------------- /examples/dino/assets/gr3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/dino/assets/gr3.png -------------------------------------------------------------------------------- /examples/nes/roms/Tetris.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/nes/roms/Tetris.nes -------------------------------------------------------------------------------- /examples/tank/assets/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/tank/assets/bg.png -------------------------------------------------------------------------------- /examples/tank/assets/box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/tank/assets/box.png -------------------------------------------------------------------------------- /examples/dino/assets/bgsky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/dino/assets/bgsky.png -------------------------------------------------------------------------------- /examples/dino/assets/dino.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/dino/assets/dino.png -------------------------------------------------------------------------------- /examples/dino/assets/dino2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/dino/assets/dino2.png -------------------------------------------------------------------------------- /examples/dino/assets/spike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/dino/assets/spike.png -------------------------------------------------------------------------------- /examples/tank/assets/brick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/tank/assets/brick.png -------------------------------------------------------------------------------- /examples/tank/assets/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/tank/assets/home.png -------------------------------------------------------------------------------- /examples/tank/assets/tank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/tank/assets/tank.png -------------------------------------------------------------------------------- /examples/tank/assets/tank2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/tank/assets/tank2.png -------------------------------------------------------------------------------- /examples/TicTacToe/assets/o2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/TicTacToe/assets/o2.png -------------------------------------------------------------------------------- /examples/TicTacToe/assets/x22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/TicTacToe/assets/x22.png -------------------------------------------------------------------------------- /examples/tank/assets/cement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/tank/assets/cement.png -------------------------------------------------------------------------------- /examples/tank/assets/bricksheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/tank/assets/bricksheet.png -------------------------------------------------------------------------------- /examples/tank/assets/magicbrick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/tank/assets/magicbrick.png -------------------------------------------------------------------------------- /examples/tank/assets/tank2 - Copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mshakir-Git/Quantumjs/HEAD/examples/tank/assets/tank2 - Copy.png -------------------------------------------------------------------------------- /core/config.js: -------------------------------------------------------------------------------- 1 | // const TW=256 2 | // const TH=240 3 | const TW=process.stdout.columns 4 | const TH=process.stdout.rows 5 | const K_TIME=30 6 | const KINEMATICS=true 7 | //TODO game.config 8 | export { 9 | TW,TH,K_TIME,KINEMATICS 10 | } -------------------------------------------------------------------------------- /soc_client.js: -------------------------------------------------------------------------------- 1 | // ES modules 2 | const { io } = require("socket.io-client"); 3 | const socket=io("ws://localhost:3000") 4 | socket.on('connect', function(){ 5 | 6 | console.log('Connected to Server') 7 | 8 | }); 9 | socket.on('msg', function(msg){ 10 | 11 | console.log('Server says'+msg) 12 | 13 | }); 14 | -------------------------------------------------------------------------------- /soc.js: -------------------------------------------------------------------------------- 1 | import { Server } from "socket.io"; 2 | 3 | import http from 'http' 4 | 5 | export const port = process.env.PORT||3000 // setting the port 6 | let server = http.createServer() 7 | let io = new Server(server) 8 | 9 | io.on('connection', (socket)=>{ 10 | console.log('New user connected'); 11 | }); 12 | setTimeout(()=>io.emit("msg","hello"),6000) 13 | server.listen(port); 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quantumjs", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "noImplicitUseStrict": true, 6 | "description": "Terminal Game Engine", 7 | "main": "index.js", 8 | "scripts": { 9 | "start": "cd examples/tank/ && node tank", 10 | "dino": "cd examples/dino/ && node dino", 11 | "nes": "cd examples/nes/ && node index", 12 | "tank": "cd examples/tank/ && node tank", 13 | "ttt": "cd examples/TicTacToe/ && node ttt" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/Mshakir-Git/Quantum.js.git" 18 | }, 19 | "keywords": [ 20 | "Terminal", 21 | "Game", 22 | "Engine", 23 | "Node", 24 | "Termux" 25 | ], 26 | "author": "shakirsibtain", 27 | "license": "ISC", 28 | "bugs": { 29 | "url": "https://github.com/Mshakir-Git/Quantum.js/issues" 30 | }, 31 | "homepage": "https://github.com/Mshakir-Git/Quantum.js#readme", 32 | "dependencies": { 33 | "jimp": "^0.16.1", 34 | "socket.io": "^4.6.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/input/input.js: -------------------------------------------------------------------------------- 1 | const mouse_enabled=false 2 | if(mouse_enabled){process.stdout.write("\x1b[?1002h")} 3 | 4 | let events=e=>{} 5 | const setEvents=(e)=>{ 6 | events=e 7 | } 8 | let mouse_ev=(k,x,y)=>{} 9 | const setMouseEvents=mev=>{mouse_ev=mev} 10 | const mouseEvent=(k,x,y)=>{if(k==0){ 11 | mouse_ev(k,(x-1),(y-1)*2) 12 | } 13 | 14 | /* 15 | console.log("Mouse Event : ",k,(x-1),(y-1)*2) 16 | const obbj=new GameObject((x-1),(y-1)*2, 1, {}) 17 | obbj.image="custom" 18 | obbj.pixels=utils.makeCircle(8,{r:250,g:0,b:20,a:255}) 19 | w.addObj(obbj)*/ 20 | } 21 | let count=0 22 | let mouseArr=[] 23 | 24 | import readline from 'readline' 25 | readline.emitKeypressEvents(process.stdin); 26 | process.stdin.setRawMode(true); 27 | process.stdin.on('keypress', (str, key) => { 28 | if (key.ctrl&&key.name=="c") { 29 | process.stdout.write("\x1b[?1002l") 30 | process.stdout.write("\x1B[?25h") 31 | process.exit(); 32 | } else { 33 | if(count){ 34 | count--;mouseArr.push(key.sequence.charCodeAt(0)-32); 35 | if(count==0){mouseEvent(...mouseArr);mouseArr=[]};return} 36 | 37 | if(key.sequence=="\x1b[M"){ 38 | //console.log(key.sequence.charCodeAt(0)-32) 39 | //console.log(key) 40 | count=3 41 | } 42 | // console.log(key) 43 | // console.log(rep(" ",30),prev_frame[0][0]) 44 | events(key) 45 | } 46 | }) 47 | 48 | export {setEvents,setMouseEvents} -------------------------------------------------------------------------------- /core/utils/utils.js: -------------------------------------------------------------------------------- 1 | import Jimp from "jimp" 2 | 3 | function rep(s,n){ 4 | let sn="" 5 | for(let i =0;i{ 12 | let equals=true 13 | Object.keys(obj1).forEach(k=>{ 14 | equals = equals&&obj1[k]==obj2[k] 15 | }) 16 | return equals 17 | } 18 | const int = n => Math.floor(n) 19 | const range = n => [...Array(n)] 20 | const makeBox=(w,h,c)=>{ 21 | return [...Array(h)].map(row=>{ 22 | return [...Array(w)].map(px=>c) 23 | }) 24 | } 25 | const makeCircle=(r,c)=>{ 26 | const d=r*2 27 | return [...Array(d)].map((row,y)=>{ 28 | return [...Array(d)].map((px,x)=>{ 29 | if(Math.sqrt((x-r)**2 + (y-r)**2)<=r &&x!=0&&y!=0) return c 30 | else return {r:0,g:0,b:0,a:0} 31 | }) 32 | }) 33 | } 34 | 35 | const cacheImagesflag=false 36 | const cacheImages={} 37 | const loadImage= async (img)=>{ 38 | if(cacheImagesflag&&cacheImages[img]){return cacheImages[img]} 39 | const image=await Jimp.read(img) 40 | const pixels=[] 41 | let row=[] 42 | image.scan(0, 0, image.bitmap.width, image.bitmap.height, function 43 | (x, y, idx) { 44 | const d=this.bitmap.data 45 | const p={r:d[idx + 0],g:d[idx + 1],b:d[idx + 2],a:d[idx + 3]}; 46 | row.push(p) 47 | if(x==image.bitmap.width-1){pixels.push(row);row=[]} 48 | }) 49 | if(cacheImagesflag){cacheImages[img]=pixels} 50 | return pixels 51 | 52 | } 53 | 54 | export default {makeBox,makeCircle,loadImage,rep,int,range} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quantum js 2 | Terminal game engine. 3 | 4 | Uses ANSI escape sequences and Unicode Block characters to render pixel graphics. 5 | 6 | 7 | 8 | 9 | Disclaimer: Ugly code ahead!! Work in progress 10 | 11 | # Getting started 12 | 13 | ## Table of contents 14 | 18 | 19 | ## Installation 20 | ```bash 21 | git clone https://github.com/Mshakir-Git/Quantumjs.git 22 | cd Quantumjs 23 | npm install #Or yarn 24 | ``` 25 | 26 | ## Running 27 | ```bash 28 | npm run start #Or yarn start 29 | npm run tank #Tank example 30 | npm run nes #NES example 31 | ``` 32 | 33 | ## NES Emulator 34 | Can be used to render nes games directly in a terminal. Uses nes-js for nes emulation. 35 | 36 | 37 | ## Funtionalities 38 | These functionalities have been implemented (some need improvements) 39 | 48 | 49 | These functionalities have NOT been implemented 50 | 55 | -------------------------------------------------------------------------------- /core/animation/animation.js: -------------------------------------------------------------------------------- 1 | class Animation { 2 | constructor(keyFrames, time, opts={}){ 3 | this.keyFrames=keyFrames 4 | this.time=time 5 | this.startTime=0 6 | this.loop=opts.loop 7 | this.iterpolate=opts.iterpolate 8 | } 9 | 10 | } 11 | const setAnimationFrame=(gameObject, currentTime)=>{ 12 | const anim=gameObject.animation 13 | if(anim.startTime==0){anim.startTime=currentTime} 14 | let elapsedTime=currentTime-anim.startTime 15 | let currentFrame={} 16 | let nextFrame={} 17 | anim.keyFrames.forEach((frame,index)=>{ 18 | if(frame.key*anim.time<=elapsedTime){ 19 | currentFrame=frame 20 | nextFrame=index+1{ 24 | //&& elapsedTime <= anim.time 25 | if(key!='key'){ 26 | const deltaTime=nextFrame?(nextFrame.key - currentFrame.key)*anim.time:0 //0.2*1000 27 | const elapsedTimeSince=nextFrame?elapsedTime- (currentFrame.key*anim.time):0//50 28 | 29 | // console.log("\n\n") 30 | // if(!nextFrame){console.log(currentFrame);process.exit()} 31 | // setTimeout(()=>{process.exit()},400) 32 | if(key == 'data'){ 33 | Object.keys(currentFrame['data']).forEach(dkey=>{ 34 | gameObject['data'][dkey]=anim.iterpolate && nextFrame?currentFrame['data'][dkey] + (nextFrame['data'][dkey]-currentFrame['data'][dkey])*(elapsedTimeSince/deltaTime):currentFrame['data'][dkey] 35 | }) 36 | } else { 37 | gameObject[key]=anim.iterpolate && nextFrame?currentFrame[key] + (nextFrame[key]-currentFrame[key])*(elapsedTimeSince/deltaTime):currentFrame[key] 38 | } 39 | } 40 | 41 | }) 42 | //loop anim 43 | if(elapsedTime>anim.time && anim.loop){ 44 | anim.startTime=currentTime 45 | elapsedTime=0 46 | } 47 | } 48 | export {Animation,setAnimationFrame} -------------------------------------------------------------------------------- /examples/TicTacToe/ttt.js: -------------------------------------------------------------------------------- 1 | import { game } from "../../index.js" 2 | 3 | const makeBox=(w,h,c,b)=>{ 4 | return [...Array(h)].map((row,y)=>{ 5 | return [...Array(w)].map((px,x)=>{ 6 | if(x==0||y==0||x==w-1||y==h-1) return b 7 | else return c 8 | }) 9 | }) 10 | } 11 | const row_reverse=a=>{ 12 | const b=a.map(aitem=>[]) 13 | a.forEach((ay,iy)=>{ 14 | ay.forEach((item,ix)=>{ 15 | b[ix][iy]=item 16 | }) 17 | }) 18 | return b 19 | } 20 | 21 | const bw=30 22 | const bh=30 23 | const s=new game.Scene([],[]) 24 | let arr=[["","",""],["","",""],["","",""]] 25 | let cellArr=[] 26 | let touchToReset=false 27 | 28 | const cell=makeBox(bw,bh,{r:50,g:50,b:50,a:255},{r:230,g:230,b:230,a:255}) 29 | const cell2=makeBox(bw,bh,{r:250,g:50,b:50,a:255},{r:230,g:230,b:230,a:255}) 30 | const cell3=makeBox(bw,bh,{r:50,g:250,b:50,a:255},{r:230,g:230,b:230,a:255}) 31 | const cell4=makeBox(bw,bh,{r:50,g:50,b:50,a:255},{r:130,g:130,b:230,a:255}) 32 | let player="x" 33 | 34 | const makeCells=()=>{ 35 | arr.forEach((iy,y)=>{ 36 | iy.forEach((ix,x)=>{ 37 | const co=new game.GameObject(x*bw,y*bh +2,3,{}) 38 | co.image="custom" 39 | co.pixels=cell 40 | cellArr.push(co) 41 | s.addObj(co) 42 | }) 43 | }) 44 | } 45 | makeCells() 46 | 47 | game.setMouseEvents((k,x,y)=>{ 48 | if(touchToReset){reset();touchToReset=false;return} 49 | arr.forEach((iy,ay)=>{ 50 | iy.forEach((ix,ax)=>{ 51 | if(ix==""&&x>ax*bw&&x<=(ax+1)*bw&&y>ay*bh&&y<=(ay+1)*bh){ 52 | // console.log(s.objs[1]) 53 | arr[ay][ax]=player 54 | // console.log(arr) 55 | const xoro=player=="x"?"x22.png":"o2.png" 56 | const co=new game.GameObject(ax*bw,ay*bh +2,1,{ 57 | image:"assets/"+xoro 58 | }) 59 | //co.image="custom" 60 | //co.pixels=player=="x"?cell2:cell3 61 | s.addObj(co) 62 | player=player=="x"?"o":"x" 63 | checkWin() 64 | } 65 | }) 66 | }) 67 | //s.remove(s.objs[3]) 68 | }) 69 | const checkWin=()=>{ 70 | arr.forEach(row=>{ 71 | if(row.toString()=="x,x,x") win("x") 72 | if(row.toString()=="o,o,o") win("o") 73 | }) 74 | row_reverse(arr).forEach(row=>{ 75 | if(row.toString()=="x,x,x") win("x") 76 | if(row.toString()=="o,o,o") win("o") 77 | }) 78 | const d1 =arr[0][0]+arr[1][1]+arr[2][2] 79 | const d2 =arr[0][2]+arr[1][1]+arr[2][0] 80 | if(d1=="xxx"||d2=="xxx") win("x") 81 | if(d1=="ooo"||d2=="ooo") win("o") 82 | } 83 | const win=(p)=>{ 84 | if(p=="x"){cellArr.forEach(c=>{c.pixels=cell4})} 85 | else {cellArr.forEach(c=>{c.pixels=cell4})} 86 | game.setHint("---------------------------------- "+p.toUpperCase()+" Won -------------------------------") 87 | touchToReset=true 88 | 89 | } 90 | const reset=()=>{ 91 | s.objs.forEach(o=>s.remove(o)) 92 | arr=arr.map(i=>["","",""]) 93 | cellArr=[] 94 | makeCells() 95 | game.setHint(" ") 96 | } 97 | game.Scene.setScene(s) 98 | game.play() 99 | -------------------------------------------------------------------------------- /testPackage.js: -------------------------------------------------------------------------------- 1 | const game = require('./index.js') 2 | //clears console and inits 3 | const makeBox=(w,h,c)=>{ 4 | return [...Array(h)].map(row=>{ 5 | return [...Array(w)].map(px=>c) 6 | }) 7 | } 8 | const cco = new game.GameObject(0, 10, 1, 9 | { 10 | text: "oooooo\no o\no o\noooooo", 11 | color: { r: 100, g: 50, b: 20 }, 12 | }) 13 | const ko = new game.GameObject(10, 20, 1, { 14 | image: "examples/dino/assets/dino.png", 15 | collision: true, 16 | // velocity: { x: 35, y: 0 }, 17 | color: { r: 100, g: 50, b: 200 }, 18 | children: [ 19 | cco 20 | ] 21 | }) 22 | const o1 = new game.GameObject(0, 2, 1,{}) 23 | const o2 = new game.GameObject(40, 2, 1,{}) 24 | const o3 = new game.GameObject(0, 42, 1,{}) 25 | const o4 = new game.GameObject(40, 42, 1,{}) 26 | o1.image="1";o1.pixels=makeBox(14,14,{r:10,g:10,b:255,a:140}) 27 | o2.image="1";o2.pixels=makeBox(14,14,{r:10,g:255,b:255,a:140}) 28 | o3.image="1";o3.pixels=makeBox(14,14,{r:10,g:255,b:10,a:140}) 29 | o4.image="1";o4.pixels=makeBox(14,14,{r:255,g:10,b:10,a:140}) 30 | 31 | const bg = new game.GameObject(0, 0, 2,{}) 32 | bg.image="1";bg.pixels=makeBox(100,100,{r:0,g:0,b:0,a:255}) 33 | 34 | 35 | 36 | // ko.image="sss" 37 | // async function l(){ko.pixels=await game.loadImage("examples/dino/assets/dino.png")}) 38 | ko.angle=270 39 | const move = new game.Animation([{ x: 20, key: 0 }, { x: 50, key: 0.4 }, { x: 53, key: 0.7 }, { x: 57, key: 1 }], 1000) 40 | 41 | const move1 = new game.Animation([{ x: 0, y: 2, key: 0 }, { x: 20, y: 20, key: 1 }], 3000) 42 | const move2 = new game.Animation([{ x: 40, y: 2, key: 0 }, { x: 20, y: 20, key: 1 }], 3000) 43 | const move3 = new game.Animation([{ x: 0, y: 42, key: 0 }, { x: 20, y: 20, key: 1 }], 3000) 44 | const move4 = new game.Animation([{ x: 40, y: 42, key: 0 }, { x: 20, y: 20, key: 1 }], 3000) 45 | 46 | const w = new game.Scene([o1,o2,o3,o4,bg], []) 47 | const wx = new game.Scene([cco], []) 48 | let input = "" 49 | w.setEvents((k) => { 50 | if (k.name == "left") { 51 | o1.y -= 0.5 52 | } 53 | if (k.name == "right") { 54 | o1.y += 0.5 55 | } 56 | if (k.name == "a") { 57 | ko.play(move) 58 | } 59 | if (k.name == "x") { 60 | o1.play(move1) 61 | o2.play(move2) 62 | o3.play(move3) 63 | o4.play(move4) 64 | } 65 | 66 | 67 | //input IMPLEMENTED 68 | // if(k.name=="return"){ 69 | // console.warn(input) 70 | // } 71 | // input+=k.sequence 72 | // game.setHint(input) 73 | 74 | if (k.name == "f") { 75 | game.setScene(wx) 76 | game.setHint("New world") 77 | //console.log() 78 | } 79 | }) 80 | wx.setEvents((k) => { 81 | if (k.name == "f") { 82 | game.setScene(w) 83 | game.setHint("Old world") 84 | //console.log() 85 | } 86 | }) 87 | game.setScene(w) 88 | game.play() 89 | 90 | //Which funcs belong to which objs 91 | //Game: setscene, play 92 | //Scene: setEvents, Update , (onCollision) 93 | //GameObject: OnCollision 94 | 95 | //scene manager: History?, transitions? 96 | //Collision: Box model? 97 | //Camera: 98 | //getters for scene name etc 99 | //game.end game.pause 100 | //User input 101 | 102 | -------------------------------------------------------------------------------- /examples/dino/dino.js: -------------------------------------------------------------------------------- 1 | 2 | import { game,renderer } from "../../index.js" 3 | //import game from './index.js' 4 | // const {setGlobalShader} =renderer 5 | // setGlobalShader((px, i)=>(i-game.vp.x)>50&&(i-game.vp.x)<100?px={...px,b:int(px.b/2)}:px) 6 | const int=n=>Math.floor(n) 7 | //const o=new game.GameObject(10,20,1,"oooooo\no o\no o\noooooo") 8 | const cco=new game.GameObject(0,-10,1, 9 | {text:"oooooo\no o\no o\noooooo", 10 | color:{r:100,g:50,b:20},tile:{x:0,y:3}}) 11 | 12 | const ko=new game.GameObject(10,11,1,{ 13 | text:"oooooo\no o\no o\noooooo", 14 | // image:"assets/dino.png", 15 | name:"dino", 16 | tile:{x:0,y:3}, 17 | collision:{bounds:[]}, 18 | velocity:{x:0,y:0}, 19 | color:{r:100,g:50,b:200}, 20 | children:[ 21 | cco 22 | ] 23 | }) 24 | const bg=new game.GameObject(0,26,3,{ 25 | image:"assets/bg1.png",tile:{x:50,y:0}}) 26 | const bgsky=new game.GameObject(0,0,4,{image:"assets/bgsky.png",tile:{x:50,y:0}}) 27 | const gr=new game.GameObject(0,46,3,{ 28 | image:"assets/bg2.png",tile:{x:50,y:0},collision:{bounds:[{x:0,y:0,w:200,h:60}]} }) 29 | 30 | const ob=new game.GameObject(0,46,2,{text:game.utils.rep("_",3000)+"\n"+game.utils.rep("- -",1000),color:{r:70,g:200,b:70}}) 31 | const cob=new game.GameObject(0,5,2,{text:"001"}) 32 | const cob2=new game.GameObject(game.vp.width-12,5,2,{text:"Score: 0"}) 33 | const spike1=new game.GameObject(game.vp.width-30,45,1, 34 | {text:" # \n#*#",tile:{x:5,y:5},collision:{bounds:[]},color:{r:209,g:0,b:20}}) 35 | //dynamic keyframes {y: funcName} if(typeof val == function) obj[key]=val() 36 | 37 | const jump = new game.Animation([{ y: ko.y, key: 0 }, { y: ko.y-6, key: 0.4 },{ y: ko.y-6, key: 0.7 }, { y: ko.y, key: 1 }], 350) 38 | 39 | const w=new game.Scene([ko,gr,bg,bgsky,spike1],[cob2]) 40 | game.Scene.setScene(w) 41 | let canjump=false 42 | w.setEvents((key)=>{ 43 | if(key.name=="f"){ 44 | /* w.addObj( 45 | new game.Kobj(ko.x+7,ko.y,2,"o",true,{x:40,y:0}) 46 | )*/ 47 | // ko.reverse() 48 | w.objs.forEach(o=>{ 49 | console.log(o.image,o.y) 50 | }) 51 | process.exit() 52 | return 53 | } 54 | if(key.name=="left"){ 55 | ko.x-=1 56 | // game.vp.x=ko.x-10 57 | } 58 | if(key.name=="right"){ 59 | ko.x+=1 60 | game.vp.x=ko.x-10 61 | } 62 | if(key.name=="up"){ 63 | ko.y-=2 64 | } 65 | if(key.name=="down"){ 66 | ko.y+=2 67 | } 68 | if(key.name=="k"&&canjump){ 69 | canjump=false 70 | ko.y-=10 71 | // ko.play(jump) 72 | // ko.y=ko.y-5 73 | // setTimeout(()=>{ko.y=ko.y+5;canjump=true},25000/(ko.velocity.x||1)) 74 | // setTimeout(()=>{canjump=true},25000/(ko.velocity.x||1)) 75 | 76 | } 77 | }) 78 | 79 | game.setCollision((a,b)=>{ 80 | if(a==ko||b==ko){ 81 | if(a==gr||b==gr){canjump=true;return} 82 | ko.velocity={x:0,y:0} 83 | ko.color="\x1b[38;2;200;0;0m" 84 | process.stdout.write("\x1b[31m") 85 | setTimeout(()=>{ 86 | process.stdout.write(" GAME OVER \x1b[37m \n") 87 | process.exit() 88 | },200) 89 | } 90 | if(a.txt=="o"&&b!=ko){ 91 | b.arr=[] 92 | } 93 | }) 94 | 95 | setInterval(()=>{ 96 | // if(canjump){ko.velocity={x:0,y:0}} 97 | // else {ko.velocity={x:0,y:10}} 98 | // game.vp.x=ko.x-10 99 | // ko.velocity.x+=0.01 100 | // game.vp.x=ko.x-10 101 | },30) 102 | 103 | let score=0 104 | let spikes=[] 105 | let objAddLoop=()=>{ 106 | spikes=spikes.filter(s=>{ 107 | if(s.x{ 5 | scene.objs.filter(o=>o.velocity).forEach(o=>{ 6 | o.x+=o.velocity.x*(K_TIME/1000) 7 | o.y+=o.velocity.y*(K_TIME/1000) 8 | }) 9 | 10 | setTimeout(()=>physicsLoop(scene),K_TIME) 11 | } 12 | 13 | 14 | 15 | 16 | let collision=(a,b,dir)=>{} 17 | const setCollision=(col_func)=>{ 18 | collision=col_func 19 | } 20 | 21 | 22 | //collision detection 23 | const checkGlobalCollisions=(colls)=>{ 24 | 25 | 26 | // const colls=filter.filter(o=>o.collision) 27 | // colls.forEach(o=>{ 28 | // colls.forEach(i=>{ 29 | // if(o!=i){ 30 | // const ix=int(i.x) 31 | // const iy=int(i.y/2) 32 | // const ox=int(o.x) 33 | // const oy=int(o.y/2) 34 | // i.arr.forEach((iarr,indyi)=>{ 35 | // o.arr.forEach((oarr,indyo)=>{ 36 | // iarr.forEach((iar,indxi)=>{ 37 | // oarr.forEach((oar,indxo)=>{ if(indxi+ix==indxo+ox&&indyi+iy==indyo+oy){ 38 | // if(oar!=" "&&iar!=" "){collision(o,i)} 39 | // } 40 | // }) 41 | // }) 42 | // }) 43 | // }) 44 | 45 | // } 46 | // }) 47 | // }) 48 | const rigidbodies=colls.filter(o=>o.collision.rigidbody) 49 | rigidbodies.forEach(o=>{ 50 | colls.forEach(i=>{ 51 | if(o!=i){ 52 | o.collision.bounds?o.collision.bounds.forEach(ob=>{ 53 | i.collision.bounds?i.collision.bounds.forEach(ib=>{ 54 | let [x,xw,y,yw]=[i.x +ib.x,i.x+(ib.w*i.scale.x),i.y+ib.y,i.y+(ib.h*i.scale.y)] 55 | let [ox,oxw,oy,oyw]=[o.x +ob.x,o.x+(ob.w*o.scale.x),o.y+ob.y,o.y+(ob.h*o.scale.y)] 56 | // if(i.name=="dino"||o.name=="dino"){} 57 | // if(x>=ox&&x<=oxw||xw>=ox&&xw<=oxw||x>=ox&&xw<=oxw||ox>=x&&oxw<=xw){ 58 | // if(y>=oy&&y<=oyw||yw>=oy&&yw<=oyw||y>=oy&&yw<=oyw||oy>=y&&oyw<=yw){ 59 | // console.log(rep(" ",20)+[x,xw,y,yw],[ox,oxw,oy,oyw]) 60 | //direction is [l,r,t,b] 61 | const ycheck=((oy=yw)||(oyw>y&&oy<=y)||(oy<=y&&oyw>=yw)||(oy>y&&oyw=xw)||(oxw>x&&ox<=x)||(ox<=x&&oxw>=xw)||(ox>x&&oxw=x&&ox<=xw&&ycheck)||(oxw>=x&&oxw<=xw&&ycheck)||(oy>=y&&oy<=yw&&xcheck)||(oyw>=y&&oyw<=yw&&xcheck)){ 64 | collision(o,i,[(ox>=x&&ox<=xw&&ycheck),(oxw>=x&&oxw<=xw&&ycheck),(oy>=y&&oy<=yw&&xcheck),(oyw>=y&&oyw<=yw&&xcheck)]) 65 | } 66 | // } 67 | // } 68 | }):null 69 | }):null 70 | // let [x,xw,y,yw]=[i.x,i.x,i.y,i.y] 71 | // if(i.pixels){xw+=i.pixels[0].length;yw+=i.pixels.length} 72 | // else {xw+=i.arr[0].length;yw+=i.arr.length*2} 73 | // let [ox,oxw,oy,oyw]=[o.x,o.x,o.y,o.y] 74 | // if(o.pixels){oxw+=o.pixels[0].length;oyw+=o.pixels.length} 75 | // else {oxw+=o.arr[0].length;oyw+=o.arr.length*2} 76 | 77 | // if(x>=ox&&x<=oxw||xw>=ox&&xw<=oxw||x>=ox&&xw<=oxw||ox>=x&&oxw<=xw){ 78 | // if(y>=oy&&y<=oyw||yw>=oy&&xw<=oyw||y>=oy&&yw<=oyw||oy>=y&&oyw<=yw){ 79 | // // console.log(rep(" ",20)+[x,xw,y,yw],[ox,oxw,oy,oyw]) 80 | // collision(o,i) 81 | // } 82 | // } 83 | 84 | 85 | } 86 | }) 87 | }) 88 | } 89 | const checkCollisions=(gameobject,scene,all=false)=>{ 90 | let collisions=[false,false,false,false] 91 | let colliders=all?scene.objs.filter(o=>o.collision):scene.visibleColliders 92 | if(colliders){ 93 | const o=gameobject 94 | colliders.forEach(i=>{ 95 | if(o!=i){ 96 | o.collision.bounds?o.collision.bounds.forEach(ob=>{ 97 | i.collision.bounds?i.collision.bounds.forEach(ib=>{ 98 | let [x,xw,y,yw]=[i.x +ib.x,i.x+(ib.w*i.scale.x),i.y+ib.y,i.y+(ib.h*i.scale.y)] 99 | let [ox,oxw,oy,oyw]=[o.x +ob.x,o.x+(ob.w*o.scale.x),o.y+ob.y,o.y+(ob.h*o.scale.y)] 100 | const ycheck=((oy=yw)||(oyw>y&&oy<=y)||(oy<=y&&oyw>=yw)||(oy>y&&oyw=xw)||(oxw>x&&ox<=x)||(ox<=x&&oxw>=xw)||(ox>x&&oxw=x&&ox<=xw&&ycheck)||(oxw>=x&&oxw<=xw&&ycheck)||(oy>=y&&oy<=yw&&xcheck)||(oyw>=y&&oyw<=yw&&xcheck)){ 103 | const newCollisions=[(ox>=x&&ox<=xw&&ycheck),(oxw>=x&&oxw<=xw&&ycheck),(oy>=y&&oy<=yw&&xcheck),(oyw>=y&&oyw<=yw&&xcheck)] 104 | collisions=collisions.map((c,i)=>c||newCollisions[i]) 105 | } 106 | }):null 107 | }):null 108 | } 109 | }) 110 | 111 | } 112 | return collisions 113 | } 114 | 115 | export {checkGlobalCollisions,checkCollisions,setCollision,physicsLoop} -------------------------------------------------------------------------------- /examples/nes/index.js: -------------------------------------------------------------------------------- 1 | import {NesJs} from "../../emulator/nes_term.js" 2 | console.log(NesJs) 3 | // import { openAsBlob } from 'node:fs'; 4 | import { open } from 'node:fs/promises'; 5 | // import {compressFrame,makeFrame,drawFrame} from './qjs/core/renderer.js' 6 | import {renderer,game} from '../../index.js' 7 | import { TW,TH } from "../../core/config.js" 8 | 9 | const {compressFrame,drawFrame} = renderer 10 | async function loadRom(url) { 11 | // e.preventDefault(); 12 | 13 | // var reader = new FileReader(); 14 | 15 | // reader.onload = function(e) { 16 | // putMessage('Loading done.'); 17 | // run(e.target.result); 18 | // }; 19 | 20 | // reader.onerror = function(e) { 21 | // for(var key in reader.error) { 22 | // putMessage(key + '=' + reader.error[key]); 23 | // } 24 | // }; 25 | const fd = await open(url); 26 | const ab = await fd.readFile() 27 | run(ab); 28 | // reader.readAsArrayBuffer(url); 29 | 30 | putMessage('') 31 | putMessage('Loading rom image...') 32 | } 33 | function loadRomx(url) { 34 | // var url = document.getElementById('romList').selectedOptions[0].value; 35 | 36 | var request = new XMLHttpRequest(); 37 | request.responseType = 'arraybuffer'; 38 | 39 | request.onload = function() { 40 | putMessage('Loading done.'); 41 | run(request.response); 42 | }; 43 | 44 | request.onerror = function(e) { 45 | putMessage('failed to load.'); 46 | }; 47 | 48 | request.open('GET', url, true); 49 | request.send(null); 50 | 51 | putMessage('') 52 | putMessage('Loading rom image...') 53 | ROMloaded(); 54 | } 55 | 56 | /** 57 | * 58 | */ 59 | function TerminalDisplay() { 60 | // this.ctx = canvas.getContext('2d'); 61 | 62 | this.width = TW; 63 | this.height =(TH-8)*2; 64 | this.offsetX=this.width<256?Math.floor((256-this.width)/2):0 65 | this.offsetY=this.height<240?Math.floor((240-this.height)/2):0 66 | 67 | // this.data = this.ctx.createImageData(this.width, this.height); 68 | // this.uint32 = new Uint32Array(this.data.data.buffer); 69 | this.data=[...Array(this.height)].map(row=>{ 70 | return [...Array(this.width)].map(c=>({r:0,g:0,b:0,a:255})) 71 | }) 72 | } 73 | Object.assign(TerminalDisplay.prototype, { 74 | isDisplay: true, 75 | 76 | /** 77 | * 78 | */ 79 | renderPixel: function(x, y, c) { 80 | // console.log(x, y, c) 81 | // var index = y * this.width + x; 82 | // this.uint32[index] = c; 83 | 84 | let color={} 85 | color['b'] = (c >> 16) & 0xff; // red 86 | color['g'] = (c >> 8) & 0xff; // green 87 | color['r'] = c & 0xff; // blue 88 | color['a']=255 89 | // if(color.r==0&&color.g==0&&color.r==0){color={r:85,g:129,b:203,a:255}} 90 | if(x=this.offsetX){ 91 | if(y=this.offsetY){ 92 | // this.data[y][x]=c 93 | this.data[y-this.offsetY][x-this.offsetX]=color 94 | } 95 | 96 | 97 | } 98 | }, 99 | 100 | /** 101 | * 102 | */ 103 | updateScreen: function() { 104 | // console.log("Update") 105 | // console.log(this.data) 106 | console.time("c") 107 | // console.log(this.data[100]) 108 | const frame=compressFrame(this.data) 109 | // console.timeEnd("c") 110 | drawFrame(frame) 111 | console.timeEnd("c") 112 | // console.log(this.data[100]) 113 | 114 | // console.log(compressFrame(this.data)) 115 | // this.ctx.putImageData(this.data, 0, 0); 116 | } 117 | }); 118 | 119 | function run(buffer) { 120 | try { 121 | var rom = new NesJs.Rom(buffer); 122 | } catch(e) { 123 | putMessage(''); 124 | putMessage(e.toString()); 125 | return; 126 | } 127 | 128 | putMessage(''); 129 | putMessage('Rom Header info'); 130 | putMessage(rom.header.dump()); 131 | 132 | let nes = new NesJs.Nes(); 133 | 134 | // nes.addEventListener('fps', function(fps) { 135 | // document.getElementById('fps').innerText = fps.toFixed(2); 136 | // }); 137 | 138 | nes.setRom(rom); 139 | 140 | nes.setDisplay(new TerminalDisplay()); 141 | 142 | try { 143 | nes.setAudio(new NesJs.Audio()); 144 | } catch(e) { 145 | putMessage(''); 146 | putMessage('Disables audio because this browser does not seems to support WebAudio.'); 147 | } 148 | game.setEvents((k)=>{ 149 | // console.log(k) 150 | nes.handleKeyUp(k.name) 151 | // if(k.name=='down'){nes.handleKeyDown();} 152 | }) 153 | // window.onkeydown = function(e) { nes.handleKeyDown(e); }; 154 | // window.onkeyup = function(e) { nes.handleKeyUp(e); }; 155 | 156 | putMessage(''); 157 | 158 | putMessage('bootup.'); 159 | nes.bootup(); 160 | 161 | putMessage('runs.'); 162 | // NESran(); 163 | nes.run(); 164 | } 165 | 166 | // put message methods 167 | 168 | /** 169 | * 170 | */ 171 | function putMessage(str) { 172 | console.log(str) 173 | // var area = document.getElementById('dump'); 174 | // area.firstChild.appendData(str + '\n'); 175 | // area.scrollTop = area.scrollHeight; 176 | } 177 | 178 | // loadRom('./roms/The Invasion.nes') 179 | // loadRom('./roms/350-in-1 (Menu).nes') 180 | // loadRom('./roms/Contra (USA).nes') 181 | loadRom('./roms/Tetris.nes') 182 | // loadRom('./roms/nestest.nes') -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | console.clear() 2 | // import {port} from "./soc.js" 3 | // console.log(port) 4 | import { TW, TH, KINEMATICS } from "./core/config.js" 5 | import { setEvents, setMouseEvents } from "./core/input/input.js" 6 | import { checkCollisions, setCollision, physicsLoop } from "./core/physics/physics.js" 7 | import { makeFrame, compressFrame, drawFrame, setGlobalShader, vp } from "./core/renderer/renderer.js" 8 | import { Animation } from "./core/animation/animation.js" 9 | import utils from "./core/utils/utils.js" 10 | const range = utils.range 11 | let current_scene = {} 12 | 13 | 14 | 15 | class Scene { 16 | constructor(newobjs, canobjs) { 17 | this.objs = [...newobjs].sort((a, b) => b.z - a.z) 18 | this.canvasObjs = [...canobjs].sort((a, b) => b.z - a.z) 19 | this.visibleColliders = [] 20 | this.events = () => { } 21 | } 22 | addObj(obj) { 23 | this.objs.push(obj) 24 | this.sort() 25 | } 26 | remove(obj) { 27 | this.objs = this.objs.filter(o => o != obj) 28 | this.sort() 29 | } 30 | sort() { 31 | //Z-Sort 32 | this.objs.sort((a, b) => b.z - a.z) 33 | } 34 | setEvents(e) { 35 | this.events = e 36 | setEvents(e) 37 | } 38 | static setScene = s => { current_scene = s; setEvents(s.events) } 39 | } 40 | //Component system (binding etc) 41 | //Cache Or precompute shaders 42 | class GameObject { 43 | //options, collider collision trigger, children 44 | constructor(x, y, z, opts) { 45 | this.x = x 46 | this.y = y 47 | this.z = z 48 | this.txt = opts.text || "" 49 | this.image = opts.image 50 | this.name = opts.name 51 | this.data = opts.data 52 | this.sprite = opts.sprite 53 | this.shader = opts.shader ? opts.shader.bind(this) : null 54 | this.scale = opts.scale ? opts.scale : { x: 1, y: 1 } 55 | this.pixels = null 56 | this.tile = opts.tile ? opts.tile : { x: 0, y: 0 } 57 | this.collision = opts.collision //micro physics 58 | this.pixels = opts.pixels ? opts.pixels : [] 59 | this.image ? this.getPixels() : null 60 | this.color = opts.color ? opts.color : { r: 255, g: 255, b: 255 } //separate into fg-bg 61 | this.velocity = opts.velocity //micro physics 62 | this.children = opts.children ? opts.children.map(function (c) { return { ...c, parent: this } }) : null 63 | this.arr = this.getText(this.txt, opts) 64 | this.setText = t => this.arr = this.getText(t) 65 | this.maxx = x + this.getMaxx([...this.arr]) 66 | this.animation = null 67 | 68 | } 69 | getText(txt, o) { 70 | const rows = this.txt.split("\n").map(i => range(this.tile.x || 1).reduce((a, t) => a += i, "").split("")) 71 | return range(this.tile.y || 1).reduce((a, rw) => a.concat(rows), []) 72 | } 73 | async getPixels() { 74 | this.pixels = await utils.loadImage(this.image) 75 | this.arr = this.pixels.reduce((acc, row, ri) => { 76 | if (ri % 2 == 0) { 77 | return [...acc, row.map(px => px.a > 0 ? "#" : " ")] 78 | } else { return acc } 79 | }, []) 80 | this.maxx = this.x + this.getMaxx([...this.arr]) 81 | if (this.collision) { 82 | if (!this.collision.bounds || this.collision.bounds.length === 0) { 83 | this.collision.bounds = [{ x: 0, y: 0, w: this.getUVpixels()[0].length, h: this.getUVpixels().length }] 84 | } 85 | } 86 | 87 | } 88 | getScaledPixels() { 89 | if (this.scaledPixels && this.scaledPixels.length == this.getUVpixels().length * this.scale.y && this.scaledPixels[0].length == this.getUVpixels()[0].length * this.scale.x) { 90 | 91 | } else { 92 | const tempArr = [] 93 | this.getUVpixels().forEach(row => { 94 | const tempRow = [] 95 | row.forEach(item => { 96 | range(Math.round(this.scale.x)).forEach(xx => { tempRow.push(item) }) 97 | }) 98 | const tempNum = range(Math.round(this.scale.y)) 99 | tempNum.forEach(yy => { tempArr.push(tempRow) }) 100 | }) 101 | this.scaledPixels = tempArr 102 | } 103 | return this.scaledPixels 104 | } 105 | getTiledPixels(pixels) { 106 | if (this.tile.x || this.tile.y) { 107 | if (this.tiledPixels && this.tiledPixels.length === this.pixels.length * this.tile.y && this.tiledPixels[0].length === this.pixels[0].length * this.tile.x) { 108 | return this.tiledPixels 109 | } else { 110 | 111 | 112 | let tempPixels = [...pixels] 113 | if (this.tile.x) { 114 | tempPixels = tempPixels.map(r => { 115 | return range(this.tile.x).reduce((a, rw) => a.concat(r), []) 116 | }) 117 | } 118 | if (this.tile.y) { 119 | tempPixels = range(this.tile.y).reduce((a, rw) => a.concat(tempPixels), []) 120 | } 121 | this.tiledPixels = tempPixels 122 | return tempPixels 123 | } 124 | } 125 | else { 126 | return pixels 127 | } 128 | } 129 | getUVpixels() { 130 | if (!this.sprite) { return this.getTiledPixels(this.pixels) } 131 | const checkUV = this.cacheUV && this.cacheUV.u === this.sprite.u && this.cacheUV.v === this.sprite.v && this.cacheUV.w === this.sprite.w && this.cacheUV.h === this.sprite.h 132 | if (this.cacheUVpixels && checkUV) { 133 | return this.cacheUVpixels 134 | } 135 | let tempPixels = [] 136 | for (let j = 0; j < this.sprite.h; j++) { 137 | let tempRow = [] 138 | for (let i = 0; i < this.sprite.w; i++) { 139 | tempRow.push(this.pixels[j + this.sprite.v][i + this.sprite.u]) 140 | // tempRow.push({r:100,g:100,b:100,a:200}) 141 | 142 | } 143 | tempPixels.push(tempRow) 144 | } 145 | this.scaledPixels = [[]] 146 | this.cacheUVpixels = this.getTiledPixels(tempPixels) 147 | this.cacheUV = { ...this.sprite } 148 | // console.log(tempPixels,this.sprite) 149 | return this.cacheUVpixels 150 | } 151 | reverse() { 152 | this.pixels = this.pixels.map(row => row.reverse()) 153 | } 154 | reverseY() { 155 | this.pixels = this.pixels.reverse() 156 | } 157 | 158 | getMaxx(arr) { 159 | return arr.sort((a, b) => b.length - a.length)[0].length 160 | } 161 | play(animation) { 162 | animation.startTime = 0 163 | this.animation = { ...animation } 164 | } 165 | checkCollisions(scene, all = false) { 166 | return checkCollisions(this, scene, all) 167 | } 168 | move(scene, x, y, all = false) { 169 | const cols = this.checkCollisions(scene, all) 170 | x < 0 && !cols[0] ? this.x += x : null 171 | x > 0 && !cols[1] ? this.x += x : null 172 | y < 0 && !cols[2] ? this.y += y : null 173 | y > 0 && !cols[3] ? this.y += y : null 174 | 175 | } 176 | } 177 | 178 | 179 | 180 | let hint = "" 181 | const drawHint = () => { 182 | hint.length > 0 ? process.stdout.write(hint.substring(0, TW) + utils.rep(" ", TW - hint.length) + "\n") : null 183 | } 184 | 185 | let gameLoop = () => { 186 | console.time() 187 | const aframe = makeFrame(current_scene) 188 | console.time("c") 189 | const frame = compressFrame(aframe) 190 | console.timeEnd("c") 191 | 192 | drawFrame(frame) 193 | drawHint() 194 | 195 | console.timeEnd() 196 | setTimeout(gameLoop, 10) 197 | } 198 | 199 | 200 | const play = () => { 201 | gameLoop() 202 | if (KINEMATICS) { 203 | physicsLoop(current_scene) 204 | } 205 | } 206 | 207 | const setHint = (h) => { 208 | hint = h 209 | } 210 | 211 | 212 | 213 | const renderer = { makeFrame, compressFrame, drawFrame, setGlobalShader } 214 | const game = { Animation, utils, Scene, GameObject, setCollision, setHint, setEvents, setMouseEvents, play, vp } 215 | export { game, renderer } 216 | 217 | -------------------------------------------------------------------------------- /examples/tank/tank.js: -------------------------------------------------------------------------------- 1 | 2 | import { game } from "../../index.js" 3 | 4 | 5 | const int = n => Math.floor(n) 6 | const makeBox = (w, h, c) => { 7 | return [...Array(h)].map(row => { 8 | return [...Array(w)].map(px => c) 9 | }) 10 | } 11 | const ko = new game.GameObject(90,190, 1, { 12 | // text:" || /n#####/n#####", 13 | image: "assets/tank2.png", 14 | collision: { bounds: [], rigidbody:true }, 15 | velocity: { x: 0, y: 0 }, 16 | color: { r: 100, g: 50, b: 200 }, 17 | data:{isgreen:false ,life:6}, 18 | shader(p){if(!this.data.isgreen){return p} return {r:p.r,g:p.g+50,b:p.b,a:255} 19 | } 20 | }) 21 | const bg = new game.GameObject(0, 6, 4, { 22 | image: "assets/bg.png", 23 | tile: 5, 24 | collision: { bounds: [{ x: 0, y: 0, w: 23, h: 26 }] } 25 | }) 26 | const blink = new game.Animation([{data:{isgreen:true}, key: 0 },{ data:{isgreen:false}, key: 0.3 },{data:{isgreen:true}, key: 0.6 },{ data:{isgreen:false}, key: 1 }],300) 27 | const blink2 = new game.Animation([{data:{isbright:true}, key: 0 },{ data:{isbright:false}, key: 0.3 },{data:{isbright:true}, key: 0.6 },{ data:{isbright:false}, key: 1 }],300) 28 | const spriteAnim = new game.Animation([{sprite:{u:10,v:0,w:10,h:10}, key: 0 },{ sprite:{u:0,v:0,w:10,h:10}, key: 0.5 },{sprite:{u:10,v:0,w:10,h:10}, key: 1 }],400,{loop:true}) 29 | 30 | const k = new game.GameObject(60, 20, 1, { 31 | // text:" # /n#####/n#####", 32 | image: "assets/tank.png", 33 | collision: {bounds: [],rigidbody:true }, 34 | scale: { x: 1, y: 1 }, 35 | velocity: { x: 0, y: 0 }, 36 | color: { r: 100, g: 50, b: 200 }, 37 | data:{life:3} 38 | // shader(p){if(p.r<100){return p} return {r:p.r,g:50,b:p.b,a:255}} 39 | 40 | }) 41 | //const box=new game.GameObject(7,7,1,{}) 42 | //box.image="custom" 43 | //box.pixels=makeBox(10,10,{r:200,g:50,b:20,a:255}) 44 | const w = new game.Scene([ko, k], []) 45 | game.Scene.setScene(w) 46 | let dir = [0, -1] 47 | const tileMatrix = [ 48 | [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], 49 | [3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 5, 3], 50 | [3, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 3], 51 | [3, 5, 2, 2, 1, 0, 0, 0, 0, 1, 2, 3], 52 | [3, 3, 3, 0, 1, 1, 2, 1, 1, 1, 2, 3], 53 | [3, 1, 1, 1, 2, 2, 1, 2, 0, 0, 2, 3], 54 | [3, 0, 1, 0, 3, 3, 3, 0, 0, 0, 5, 3], 55 | [3, 0, 2, 2, 0, 2, 1, 1, 0, 0, 0, 3], 56 | [3, 0, 1, 1, 2, 2, 2, 2, 2, 0, 0, 3], 57 | [3, 0, 0, 0, 5, 5, 5, 0, 0, 0, 0, 3], 58 | [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], 59 | 60 | ] 61 | const brick=new game.GameObject(0, 0, 1, { image: "assets/tank2.png",pixels:[] }) 62 | const mbrick={} 63 | const cement={} 64 | const tiles=[brick,mbrick,cement] 65 | const scale = (matrix, s) => { 66 | const tempArr = [] 67 | matrix.forEach(row => { 68 | const tempRow = [] 69 | row.forEach(item => { 70 | [...Array(Math.round(s))].forEach(xx => { tempRow.push(item) }) 71 | }) 72 | const tempNum = [...Array(Math.round(s))] 73 | tempNum.forEach(yy => { tempArr.push(tempRow) }) 74 | }) 75 | return tempArr 76 | 77 | } 78 | //make map 79 | scale(tileMatrix, 2).forEach((row, y) => { 80 | row.forEach((n, x) => { 81 | if (n > 0) { 82 | const width = 10 83 | const heigth = 10 84 | 85 | // new game.GameObject(x * width, y * heigth, 1, { pixels: makeBox(width, heigth, { r: n * 100, g: 100 * 2 % n, b: 50, a: 255 }) }) 86 | n===1?w.addObj(new game.GameObject(x * width, y * heigth, 1, { image: "assets/brick.png", collision:{} ,data:{life:1,isbright:false},shader(p){if(!this.data.isbright){return p} return {r:p.r+50,g:p.g,b:p.b,a:255} }})):null 87 | n===2?w.addObj(new game.GameObject(x * width, y * heigth, 1, { image: "assets/bricksheet.png", sprite:{u:10,v:0,w:10,h:10} , collision:{}, data:{life:2,isbright:false},shader(p){if(!this.data.isbright){return p} return {r:p.r+50,g:p.g,b:p.b,a:255} }})):null 88 | n===3?w.addObj(new game.GameObject(x * width, y * heigth, 1, { image: "assets/cement.png" , collision:{}, data:{life:1000,isbright:false},shader(p){if(!this.data.isbright){return p} return {r:p.r+50,g:p.g+50,b:p.b,a:255} }})):null 89 | 90 | } else if(Math.random()<0.2){ 91 | const width = 10 92 | const heigth = 10 93 | w.addObj(new game.GameObject(x * width, y * heigth, 1, { image: "assets/brick.png" , collision:{}, data:{life:1,isbright:false},shader(p){if(!this.data.isbright){return p} return {r:p.r+50,g:p.g,b:p.b,a:255} }})) 94 | } 95 | }) 96 | }) 97 | //make home 98 | w.addObj(new game.GameObject(100, 180, 1, { image: "assets/bricksheet.png", sprite:{u:10,v:0,w:10,h:10} , collision:{}, data:{life:2,isbright:false},shader(p){if(!this.data.isbright){return p} return {r:p.r+50,g:p.g,b:p.b,a:255} }})) 99 | w.addObj(new game.GameObject(100, 190, 1, { image: "assets/bricksheet.png", sprite:{u:10,v:0,w:10,h:10} , collision:{}, data:{life:2,isbright:false},shader(p){if(!this.data.isbright){return p} return {r:p.r+50,g:p.g,b:p.b,a:255} }})) 100 | w.addObj(new game.GameObject(110, 180, 1, { image: "assets/bricksheet.png", sprite:{u:10,v:0,w:10,h:10} , collision:{}, data:{life:2,isbright:false},shader(p){if(!this.data.isbright){return p} return {r:p.r+50,g:p.g,b:p.b,a:255} }})) 101 | w.addObj(new game.GameObject(120, 180,1, { image: "assets/bricksheet.png", sprite:{u:10,v:0,w:10,h:10} , collision:{}, data:{life:2,isbright:false},shader(p){if(!this.data.isbright){return p} return {r:p.r+50,g:p.g,b:p.b,a:255} }})) 102 | w.addObj(new game.GameObject(120, 190 , 1, { image: "assets/bricksheet.png", sprite:{u:10,v:0,w:10,h:10} , collision:{}, data:{life:2,isbright:false},shader(p){if(!this.data.isbright){return p} return {r:p.r+50,g:p.g,b:p.b,a:255} }})) 103 | 104 | w.addObj(new game.GameObject(110, 190 , 1, { image: "assets/home.png" , collision:{}, data:{life:2,isbright:false},shader(p){if(!this.data.isbright){return p} return {r:p.r+50,g:p.g,b:p.b,a:255} }})) 105 | 106 | let showColliders = (o) => { 107 | o.collision.bounds.forEach(b => { 108 | w.addObj(new game.GameObject(o.x + b.x, o.y + b.y, o.z, { pixels: makeBox(b.w, b.h, { r: 250, g: 0, b: 20, a: 100 }) })) 109 | }) 110 | } 111 | 112 | // setTimeout(()=>showColliders(ko),1000) 113 | w.setEvents((key) => { 114 | const cols=ko.checkCollisions(w) 115 | // game.setHint(cols+"") 116 | if (key.name == "f") { 117 | /* w.addObj( 118 | new game.Kobj(ko.x+7,ko.y,2,"o",true,{x:40,y:0}) 119 | )*/ 120 | // ko.reverse() 121 | w.objs.forEach(o => { 122 | console.log(o.image, o.y) 123 | }) 124 | process.exit() 125 | return 126 | } 127 | if (key.name == "left") { 128 | 129 | ko.move(w,-1,0) 130 | // ko.x -= 1 131 | ko.angle = 270 132 | dir = [-1, 0] 133 | 134 | ko.xgame.vp.x+game.vp.width-20?game.vp.x=ko.x-20:null 142 | 143 | // game.vp.x=ko.x-10 144 | } 145 | if (key.name == "up") { 146 | ko.move(w,0,-1) 147 | ko.angle = 0 148 | dir = [0, -1] 149 | ko.ygame.vp.y+game.vp.height-20?game.vp.y=ko.y-20:null 157 | 158 | } 159 | 160 | 161 | if (key.name == "m") { 162 | const xd = dir[0] == 0 ? 2 + (1 + dir[1]) / 2 : (1 + dir[0]) * 2 163 | const yd = dir[1] == 0 ? 3 + (1 - dir[0]) / 2 : (1 + dir[1]) * 2 164 | const bullet = new game.GameObject(ko.x + xd, ko.y + yd, ko.z + 1, { 165 | velocity: { x: 30 * dir[0], y: 30 * dir[1] }, 166 | collision: { bounds: [{ x: 0, y: 0, w: 1, h: 1 }] ,rigidbody:true } 167 | }) 168 | bullet.image = "bullet" 169 | bullet.arr = [["#"]] 170 | bullet.pixels = makeBox(1, 1, { r: 250, g: 0, b: 20, a: 255 }) 171 | // showColliders(bg) 172 | w.addObj(bullet) 173 | //game.setHint("hello") 174 | } 175 | 176 | }) 177 | k.data={} 178 | const angleList={"0/-1":0,"0/1":180,"1/0":90,"-1/0":270,} 179 | const t=Math.round(Math.random()) 180 | k.data.direction=[(Math.round(Math.random())*2 -1)*t,(Math.round(Math.random())*2 -1)*(t==0?1:0)] 181 | k.angle=angleList[k.data.direction[0]+"/"+k.data.direction[1]] 182 | 183 | // const ray=new game.GameObject(k.x+2,k.y-10,k.z,{pixels:makeBox(1, 8, { r: 250, g: 0, b: 20, a: 200 }),collision:{rigidbody:true,bounds:[{x:0,y:0,w:1,h:8}]}}) 184 | // w.addObj(ray) 185 | setInterval(()=>{ 186 | // const cols=k.checkCollisions(w) 187 | // if(cols.indexOf(true)!=-1){ 188 | // const r=cols.map(c=>c?1:0) 189 | // k.move(r[0]-r[1],r[2]-r[3]) 190 | const t=Math.round(Math.random()) 191 | k.data.direction=[(Math.round(Math.random())*2 -1)*t,(Math.round(Math.random())*2 -1)*(t==0?1:0)] 192 | k.angle=angleList[k.data.direction[0]+"/"+k.data.direction[1]] 193 | // k.data.direction=[r[2]+r[3],r[0]-r[1]] 194 | 195 | // } 196 | 197 | 198 | // console.log(" "+ray.checkCollisions(w)) 199 | },3000) 200 | setInterval(()=>{ 201 | if(Math.random()<0.2){shoot(k)} 202 | k.move(w,k.data.direction[0],k.data.direction[1],true) 203 | },100) 204 | function shoot(go){ 205 | const dir=go.data.direction||[0,1] 206 | const xd = dir[0] == 0 ? 2 + (1 + dir[1]) / 2 : (1 + dir[0]) * 2 207 | const yd = dir[1] == 0 ? 3 + (1 - dir[0]) / 2 : (1 + dir[1]) * 2 208 | const bullet = new game.GameObject(go.x + xd, go.y + yd, go.z + 1, { 209 | velocity: { x: 30 * dir[0], y: 30 * dir[1] }, 210 | collision: { bounds: [{ x: 0, y: 0, w: 1, h: 1 }] ,rigidbody:true } 211 | }) 212 | bullet.image = "ebullet" 213 | bullet.arr = [["#"]] 214 | bullet.pixels = makeBox(1, 1, { r: 250, g: 0, b: 20, a: 255 }) 215 | // showColliders(bg) 216 | w.addObj(bullet) 217 | //game.setHint("hello") 218 | 219 | } 220 | game.setCollision((a, b, dir) => { 221 | 222 | if (a.image == "bullet" && b != ko && b != bg) { 223 | // a.pixels = [[]] 224 | // b.pixels = [[]] 225 | b.data?b.data.life--:null; 226 | b.data?b.play(blink2):null 227 | // if(b.sprite){b.sprite.u=0;console.log(b.sprite)} 228 | // if(b.sprite){b.play(spriteAnim)} 229 | 230 | if(b.data&&b.data.life<=0){w.remove(b)} 231 | w.remove(a) 232 | // w.remove(b) 233 | 234 | } 235 | 236 | if (a.image == "ebullet" && b == ko){ 237 | ko.play(blink) 238 | game.setHint("GAME OVER") 239 | // b.data?b.data.life--:null; 240 | // // b.data?b.play(blink):null 241 | // if(b.data&&b.data.life<=0){w.remove(b);game.setHint("GAME OVER")} 242 | } 243 | if (a.image == "ebullet" && b != k && b != bg) { 244 | // a.pixels = [[]] 245 | // b.pixels = [[]] 246 | b.data?b.data.life--:null; 247 | b.data?b.play(blink2):null 248 | // if(b.sprite){b.sprite.u=0;console.log(b.sprite)} 249 | // if(b.sprite){b.play(spriteAnim)} 250 | 251 | if(b.data&&b.data.life<=0){w.remove(b)} 252 | w.remove(a) 253 | // w.remove(b) 254 | 255 | } 256 | 257 | }) 258 | game.play() 259 | -------------------------------------------------------------------------------- /core/renderer/renderer.js: -------------------------------------------------------------------------------- 1 | import { TW, TH } from "../config.js" 2 | import { setAnimationFrame } from "../animation/animation.js" 3 | import { checkGlobalCollisions } from "../physics/physics.js" 4 | import utils from '../utils/utils.js' 5 | const { int, range } = utils 6 | 7 | 8 | // width:TW,height:(TH-8)*2, 9 | 10 | //TODO: Take this common 11 | const viewport = { 12 | x: 0, y: 0, 13 | width: TW, height: (TH - 8) * 2, 14 | } 15 | // width:TW,height:(TH-6>70?70:TH-6)*2, 16 | 17 | const vp = viewport 18 | //empty_frame might cause pass by ref issues 19 | const empty_frame = range(viewport.height).map(_ => { 20 | return range(viewport.width).map(_ => { return { r: 0, g: 0, b: 0, a: 0 } }) 21 | }) 22 | //store prev_frame (for compress) 23 | let prev_frame = range(int(viewport.height / 2)).map(y => { 24 | return range(viewport.width).map(xi => { return { fg: { r: 0, g: 0, b: 0, a: 0 }, bg: { r: 0, g: 0, b: 0, a: 0 }, c: "\u2580" } }) 25 | }) 26 | 27 | 28 | //TODO: Move shade 29 | //Global shader 30 | function shade(px, i, j, frame) { 31 | return px 32 | //Example shader 33 | // return (i-vp.x)>50&&(i-vp.x)<100?px={...px,b:int(px.b/2)}:px 34 | } 35 | const setGlobalShader = (shader) => { shade = shader } 36 | 37 | const compressFrame = (frame) => { 38 | //return console.log(frame[0][0],frame[22][0]) 39 | let pfg = { r: "s", g: "s", b: "s" } 40 | let pbg = { r: "s", g: "s", b: "s" } 41 | let frameStr = "" 42 | let j = 0 43 | let new_prev = [] 44 | let moved_already = false 45 | while (j < frame.length) { 46 | let row = frame[j] 47 | let row2 = frame[j + 1] 48 | let row_prev = prev_frame[int(j / 2)] 49 | let new_prev_row = [] 50 | moved_already = false 51 | 52 | let rowStr = "" 53 | 54 | let i = row.length + 1 55 | 56 | while (--i) { 57 | let px = {} 58 | if (row[row.length - i].c || row2[row2.length - i].c) { 59 | //Ascii 60 | if (row[row.length - i].c) { px = row[row.length - i] } 61 | if (row2[row2.length - i].c) { px = row2[row2.length - i] } 62 | 63 | } 64 | else { 65 | 66 | //pixel based 67 | let px_top = shade(row[row.length - i], row.length - i, frame.length - j, frame) 68 | let px_bottom = shade(row2[row2.length - i], row.length - i, frame.length - j, frame) //fix j coords 69 | px = combinePixel(px_top, px_bottom) 70 | } 71 | //Diff rendering (check with prev frame) 72 | new_prev_row.push(px) 73 | let moveTostr = "" 74 | let px_prev = row_prev[row.length - i] 75 | if (px.fg.r == px_prev.fg.r && px.fg.b == px_prev.fg.b && px.fg.g == px_prev.fg.g && px.bg.r == px_prev.bg.r && px.bg.b == px_prev.bg.b && px.bg.g == px_prev.bg.g && px.c == px_prev.c) { 76 | moved_already = false 77 | continue 78 | } else { 79 | if (!moved_already) { 80 | moveTostr = `\x1b[${int(j / 2)};${row.length - i + 1}H` 81 | moved_already = true 82 | } 83 | } 84 | 85 | 86 | let fgstr = (px.fg.r == pfg.r && px.fg.b == pfg.b && px.fg.g == pfg.g) ? "" : `\x1b[38;2;${px.fg.r};${px.fg.g};${px.fg.b}m` 87 | let bgstr = (px.bg.r == pbg.r && px.bg.b == pbg.b && px.bg.g == pbg.g) ? "" : `\x1b[48;2;${px.bg.r};${px.bg.g};${px.bg.b}m` 88 | pfg = { ...px.fg } 89 | pbg = { ...px.bg } 90 | 91 | rowStr += px.c ? `${moveTostr}${fgstr}${bgstr}${px.c}` : `${moveTostr}${fgstr}${bgstr} ` 92 | 93 | 94 | 95 | } 96 | new_prev.push(new_prev_row) 97 | frameStr += rowStr + "" 98 | 99 | j += 2 100 | } 101 | // set cursor to (TW,TH) 102 | prev_frame = new_prev 103 | return frameStr + `\x1b[${int(vp.height / 2)};${vp.width}H` 104 | } 105 | 106 | const combinePixel = (p1, p2) => { 107 | //Combine 2 pixels int fg,bg,c 108 | let p1n = p1.a > 0 ? p1 : { r: 0, g: 0, b: 0 } 109 | let p2n = p2.a > 0 ? p2 : { r: 0, g: 0, b: 0 } 110 | let c = " " 111 | if (p1n.r == p2n.r && p1n.g == p2n.g && p1n.b == p2n.b) { 112 | c = " " 113 | } else { 114 | c = "\u2580" 115 | } 116 | return { fg: p1, bg: p2, c: c } 117 | 118 | } 119 | 120 | //mix alpha for tranparency 121 | const mixPixels = (p1, p2) => { 122 | if (!p1) { return p2 } 123 | const p2_o = p2.a / 255 124 | const p1_o = 1 - p2_o 125 | return { r: int(p1.r * p1_o) + int(p2.r * p2_o), g: int(p1.g * p1_o) + int(p2.g * p2_o), b: int(p1.b * p1_o) + int(p2.b * p2_o), a: 255 } 126 | } 127 | 128 | const makeFrame = (w) => { 129 | //new empty frame 130 | const frame = empty_frame.map(i => [...i]) 131 | //filter all objs and get currently visible ones 132 | /* const allobjs=w.objs.reduce((all,o)=> 133 | o.children?all.concat([o,...o.children]):all.concat([o] 134 | ),[])*/ 135 | const filter = w.objs.filter(item => { 136 | const inx = (viewport.x > item.x && 137 | viewport.x <= item.maxx) || 138 | (viewport.x + viewport.width > item.x && 139 | viewport.x + viewport.width <= item.maxx) || 140 | (viewport.x <= item.x && 141 | viewport.x + viewport.width > item.maxx) 142 | 143 | const iny = (viewport.y > item.y && 144 | viewport.y <= item.arr.length * 2) || 145 | (viewport.y + viewport.height > item.y && 146 | viewport.y + viewport.height <= item.arr.length * 2) || 147 | (viewport.y <= item.y && 148 | viewport.y + viewport.height > item.arr.length * 2) 149 | return inx && iny 150 | }) 151 | 152 | //Add Debug logs 153 | console.time("collision") 154 | 155 | const colls = filter.filter(o => o.collision) 156 | w.visibleColliders = colls 157 | checkGlobalCollisions(colls) 158 | console.timeEnd("collision") 159 | console.time("img") 160 | //for each object fill the respective cells in frame 161 | filter.forEach(object => { 162 | 163 | //Animations: Set current Animation frame 164 | if (object.animation) { setAnimationFrame(object, +new Date()) } 165 | 166 | //Object x,y relative to camera 167 | const vx = int(object.x - viewport.x) 168 | const vy = int(object.y - viewport.y) 169 | 170 | if (!object.txt) { 171 | //Pixel Based 172 | if (object.pixels && object.pixels.length) { 173 | const rows = object.getScaledPixels() //Full Pixel Image 174 | 175 | //Loop over rows within viewport height 176 | const y_screen_start = vy < 0 ? -vy : 0 177 | const y_screen_end = (rows.length > -vy + vp.height ? -vy + vp.height : rows.length) 178 | for (let y_index = y_screen_start; y_index < y_screen_end; y_index++) { 179 | const row = rows[y_index] 180 | //loop over pixels within viewport width 181 | const x_screen_start = vx < 0 ? -vx : 0 182 | const x_screen_end = (row.length > -vx + vp.width ? -vx + vp.width : row.length) 183 | for (let x_index = x_screen_start; x_index < x_screen_end; x_index++) { 184 | // if(object.x+x_index>=vp.x&&vx+x_index= vp.x && vx + newX < viewport.width && object.y + newY >= vp.y && vy + newY < viewport.height) { 204 | //shift above condition to above for loops(once done) 205 | if (px.a > 0 && frame[vy + newY]) { 206 | let prev_px = frame[vy + newY][vx + newX] 207 | if (prev_px.c) { prev_px = prev_px.bg } 208 | frame[vy + newY][vx + newX] = mixPixels(prev_px, object.shader ? object.shader(px, x_index, y_index, object.pixels) : px) 209 | } 210 | } 211 | 212 | // } 213 | } 214 | } 215 | } 216 | } else { 217 | //Text Based 218 | object.arr.forEach((row, y_index) => 219 | row.forEach((tx, x_index) => { 220 | 221 | if (vx + x_index < viewport.width && vx + x_index >= 0) { 222 | //set txt bg in GameObject (fix out of bounds error) 223 | if (frame[vy + y_index * 2]) { 224 | const prev_px = frame[vy + y_index * 2][vx + x_index] 225 | const bg = prev_px && prev_px.bg ? prev_px.bg : prev_px 226 | frame[vy + y_index * 2][vx + x_index] = { fg: object.color, bg: bg, c: tx } 227 | } 228 | 229 | } 230 | }) 231 | ) 232 | 233 | } 234 | 235 | 236 | // children part (ascii ony) 237 | 238 | // i.children?i.children.forEach(j=>{ 239 | 240 | // const jvx=int(vx+j.x) 241 | // const jvy=int(vy+j.y) 242 | // j.arr.forEach((ii,indy)=> 243 | // ii.forEach((tx,indx)=>{ 244 | // if(jvx+indx { 258 | const vx = int(c_object.x) 259 | const vy = int(c_object.y) 260 | c_object.arr.forEach((row, y_index) => 261 | row.forEach((txt, x_index) => { 262 | if (vx + x_index < viewport.width) { 263 | const prev_px = frame[vy + y_index][vx + x_index] 264 | const bg = prev_px.c ? prev_px.bg : prev_px 265 | frame[vy + y_index][vx + x_index] = { fg: c_object.color, bg: bg, c: txt } 266 | } 267 | 268 | }) 269 | ) 270 | }) 271 | 272 | return frame 273 | } 274 | const drawFrame = (frame) => { 275 | process.stdout.write('\x1b[H\x1B[?25l' + frame + '\n' + '\x1b[37m\x1b[48;2;0;0;0m') 276 | } 277 | export { compressFrame, makeFrame, drawFrame, setGlobalShader, vp } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/runtime@^7.7.2": 6 | version "7.17.2" 7 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" 8 | integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== 9 | dependencies: 10 | regenerator-runtime "^0.13.4" 11 | 12 | "@jimp/bmp@^0.16.1": 13 | version "0.16.1" 14 | resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.16.1.tgz#6e2da655b2ba22e721df0795423f34e92ef13768" 15 | integrity sha512-iwyNYQeBawrdg/f24x3pQ5rEx+/GwjZcCXd3Kgc+ZUd+Ivia7sIqBsOnDaMZdKCBPlfW364ekexnlOqyVa0NWg== 16 | dependencies: 17 | "@babel/runtime" "^7.7.2" 18 | "@jimp/utils" "^0.16.1" 19 | bmp-js "^0.1.0" 20 | 21 | "@jimp/core@^0.16.1": 22 | version "0.16.1" 23 | resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.16.1.tgz#68c4288f6ef7f31a0f6b859ba3fb28dae930d39d" 24 | integrity sha512-la7kQia31V6kQ4q1kI/uLimu8FXx7imWVajDGtwUG8fzePLWDFJyZl0fdIXVCL1JW2nBcRHidUot6jvlRDi2+g== 25 | dependencies: 26 | "@babel/runtime" "^7.7.2" 27 | "@jimp/utils" "^0.16.1" 28 | any-base "^1.1.0" 29 | buffer "^5.2.0" 30 | exif-parser "^0.1.12" 31 | file-type "^9.0.0" 32 | load-bmfont "^1.3.1" 33 | mkdirp "^0.5.1" 34 | phin "^2.9.1" 35 | pixelmatch "^4.0.2" 36 | tinycolor2 "^1.4.1" 37 | 38 | "@jimp/custom@^0.16.1": 39 | version "0.16.1" 40 | resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.16.1.tgz#28b659c59e20a1d75a0c46067bd3f4bd302cf9c5" 41 | integrity sha512-DNUAHNSiUI/j9hmbatD6WN/EBIyeq4AO0frl5ETtt51VN1SvE4t4v83ZA/V6ikxEf3hxLju4tQ5Pc3zmZkN/3A== 42 | dependencies: 43 | "@babel/runtime" "^7.7.2" 44 | "@jimp/core" "^0.16.1" 45 | 46 | "@jimp/gif@^0.16.1": 47 | version "0.16.1" 48 | resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.16.1.tgz#d1f7c3a58f4666482750933af8b8f4666414f3ca" 49 | integrity sha512-r/1+GzIW1D5zrP4tNrfW+3y4vqD935WBXSc8X/wm23QTY9aJO9Lw6PEdzpYCEY+SOklIFKaJYUAq/Nvgm/9ryw== 50 | dependencies: 51 | "@babel/runtime" "^7.7.2" 52 | "@jimp/utils" "^0.16.1" 53 | gifwrap "^0.9.2" 54 | omggif "^1.0.9" 55 | 56 | "@jimp/jpeg@^0.16.1": 57 | version "0.16.1" 58 | resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.16.1.tgz#3b7bb08a4173f2f6d81f3049b251df3ee2ac8175" 59 | integrity sha512-8352zrdlCCLFdZ/J+JjBslDvml+fS3Z8gttdml0We759PnnZGqrnPRhkOEOJbNUlE+dD4ckLeIe6NPxlS/7U+w== 60 | dependencies: 61 | "@babel/runtime" "^7.7.2" 62 | "@jimp/utils" "^0.16.1" 63 | jpeg-js "0.4.2" 64 | 65 | "@jimp/plugin-blit@^0.16.1": 66 | version "0.16.1" 67 | resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-0.16.1.tgz#09ea919f9d326de3b9c2826fe4155da37dde8edb" 68 | integrity sha512-fKFNARm32RoLSokJ8WZXHHH2CGzz6ire2n1Jh6u+XQLhk9TweT1DcLHIXwQMh8oR12KgjbgsMGvrMVlVknmOAg== 69 | dependencies: 70 | "@babel/runtime" "^7.7.2" 71 | "@jimp/utils" "^0.16.1" 72 | 73 | "@jimp/plugin-blur@^0.16.1": 74 | version "0.16.1" 75 | resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-0.16.1.tgz#e614fa002797dcd662e705d4cea376e7db968bf5" 76 | integrity sha512-1WhuLGGj9MypFKRcPvmW45ht7nXkOKu+lg3n2VBzIB7r4kKNVchuI59bXaCYQumOLEqVK7JdB4glaDAbCQCLyw== 77 | dependencies: 78 | "@babel/runtime" "^7.7.2" 79 | "@jimp/utils" "^0.16.1" 80 | 81 | "@jimp/plugin-circle@^0.16.1": 82 | version "0.16.1" 83 | resolved "https://registry.yarnpkg.com/@jimp/plugin-circle/-/plugin-circle-0.16.1.tgz#20e3194a67ca29740aba2630fd4d0a89afa27491" 84 | integrity sha512-JK7yi1CIU7/XL8hdahjcbGA3V7c+F+Iw+mhMQhLEi7Q0tCnZ69YJBTamMiNg3fWPVfMuvWJJKOBRVpwNTuaZRg== 85 | dependencies: 86 | "@babel/runtime" "^7.7.2" 87 | "@jimp/utils" "^0.16.1" 88 | 89 | "@jimp/plugin-color@^0.16.1": 90 | version "0.16.1" 91 | resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-0.16.1.tgz#0f298ba74dee818b663834cd80d53e56f3755233" 92 | integrity sha512-9yQttBAO5SEFj7S6nJK54f+1BnuBG4c28q+iyzm1JjtnehjqMg6Ljw4gCSDCvoCQ3jBSYHN66pmwTV74SU1B7A== 93 | dependencies: 94 | "@babel/runtime" "^7.7.2" 95 | "@jimp/utils" "^0.16.1" 96 | tinycolor2 "^1.4.1" 97 | 98 | "@jimp/plugin-contain@^0.16.1": 99 | version "0.16.1" 100 | resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-0.16.1.tgz#3c5f5c495fd9bb08a970739d83694934f58123f2" 101 | integrity sha512-44F3dUIjBDHN+Ym/vEfg+jtjMjAqd2uw9nssN67/n4FdpuZUVs7E7wadKY1RRNuJO+WgcD5aDQcsvurXMETQTg== 102 | dependencies: 103 | "@babel/runtime" "^7.7.2" 104 | "@jimp/utils" "^0.16.1" 105 | 106 | "@jimp/plugin-cover@^0.16.1": 107 | version "0.16.1" 108 | resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-0.16.1.tgz#0e8caec16a40abe15b1b32e5383a603a3306dc41" 109 | integrity sha512-YztWCIldBAVo0zxcQXR+a/uk3/TtYnpKU2CanOPJ7baIuDlWPsG+YE4xTsswZZc12H9Kl7CiziEbDtvF9kwA/Q== 110 | dependencies: 111 | "@babel/runtime" "^7.7.2" 112 | "@jimp/utils" "^0.16.1" 113 | 114 | "@jimp/plugin-crop@^0.16.1": 115 | version "0.16.1" 116 | resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-0.16.1.tgz#b362497c873043fe47ba881ab08604bf7226f50f" 117 | integrity sha512-UQdva9oQzCVadkyo3T5Tv2CUZbf0klm2cD4cWMlASuTOYgaGaFHhT9st+kmfvXjKL8q3STkBu/zUPV6PbuV3ew== 118 | dependencies: 119 | "@babel/runtime" "^7.7.2" 120 | "@jimp/utils" "^0.16.1" 121 | 122 | "@jimp/plugin-displace@^0.16.1": 123 | version "0.16.1" 124 | resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-0.16.1.tgz#4dd9db518c3e78de9d723f86a234bf98922afe8d" 125 | integrity sha512-iVAWuz2+G6Heu8gVZksUz+4hQYpR4R0R/RtBzpWEl8ItBe7O6QjORAkhxzg+WdYLL2A/Yd4ekTpvK0/qW8hTVw== 126 | dependencies: 127 | "@babel/runtime" "^7.7.2" 128 | "@jimp/utils" "^0.16.1" 129 | 130 | "@jimp/plugin-dither@^0.16.1": 131 | version "0.16.1" 132 | resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-0.16.1.tgz#b47de2c0bb09608bed228b41c3cd01a85ec2d45b" 133 | integrity sha512-tADKVd+HDC9EhJRUDwMvzBXPz4GLoU6s5P7xkVq46tskExYSptgj5713J5Thj3NMgH9Rsqu22jNg1H/7tr3V9Q== 134 | dependencies: 135 | "@babel/runtime" "^7.7.2" 136 | "@jimp/utils" "^0.16.1" 137 | 138 | "@jimp/plugin-fisheye@^0.16.1": 139 | version "0.16.1" 140 | resolved "https://registry.yarnpkg.com/@jimp/plugin-fisheye/-/plugin-fisheye-0.16.1.tgz#f625047b6cdbe1b83b89e9030fd025ab19cdb1a4" 141 | integrity sha512-BWHnc5hVobviTyIRHhIy9VxI1ACf4CeSuCfURB6JZm87YuyvgQh5aX5UDKtOz/3haMHXBLP61ZBxlNpMD8CG4A== 142 | dependencies: 143 | "@babel/runtime" "^7.7.2" 144 | "@jimp/utils" "^0.16.1" 145 | 146 | "@jimp/plugin-flip@^0.16.1": 147 | version "0.16.1" 148 | resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-0.16.1.tgz#7a99ea22bde802641017ed0f2615870c144329bb" 149 | integrity sha512-KdxTf0zErfZ8DyHkImDTnQBuHby+a5YFdoKI/G3GpBl3qxLBvC+PWkS2F/iN3H7wszP7/TKxTEvWL927pypT0w== 150 | dependencies: 151 | "@babel/runtime" "^7.7.2" 152 | "@jimp/utils" "^0.16.1" 153 | 154 | "@jimp/plugin-gaussian@^0.16.1": 155 | version "0.16.1" 156 | resolved "https://registry.yarnpkg.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.16.1.tgz#0845e314085ccd52e34fad9a83949bc0d81a68e8" 157 | integrity sha512-u9n4wjskh3N1mSqketbL6tVcLU2S5TEaFPR40K6TDv4phPLZALi1Of7reUmYpVm8mBDHt1I6kGhuCJiWvzfGyg== 158 | dependencies: 159 | "@babel/runtime" "^7.7.2" 160 | "@jimp/utils" "^0.16.1" 161 | 162 | "@jimp/plugin-invert@^0.16.1": 163 | version "0.16.1" 164 | resolved "https://registry.yarnpkg.com/@jimp/plugin-invert/-/plugin-invert-0.16.1.tgz#7e6f5a15707256f3778d06921675bbcf18545c97" 165 | integrity sha512-2DKuyVXANH8WDpW9NG+PYFbehzJfweZszFYyxcaewaPLN0GxvxVLOGOPP1NuUTcHkOdMFbE0nHDuB7f+sYF/2w== 166 | dependencies: 167 | "@babel/runtime" "^7.7.2" 168 | "@jimp/utils" "^0.16.1" 169 | 170 | "@jimp/plugin-mask@^0.16.1": 171 | version "0.16.1" 172 | resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-0.16.1.tgz#e7f2460e05c3cda7af5e76f33ccb0579f66f90df" 173 | integrity sha512-snfiqHlVuj4bSFS0v96vo2PpqCDMe4JB+O++sMo5jF5mvGcGL6AIeLo8cYqPNpdO6BZpBJ8MY5El0Veckhr39Q== 174 | dependencies: 175 | "@babel/runtime" "^7.7.2" 176 | "@jimp/utils" "^0.16.1" 177 | 178 | "@jimp/plugin-normalize@^0.16.1": 179 | version "0.16.1" 180 | resolved "https://registry.yarnpkg.com/@jimp/plugin-normalize/-/plugin-normalize-0.16.1.tgz#032dfd88eefbc4dedc8b1b2d243832e4f3af30c8" 181 | integrity sha512-dOQfIOvGLKDKXPU8xXWzaUeB0nvkosHw6Xg1WhS1Z5Q0PazByhaxOQkSKgUryNN/H+X7UdbDvlyh/yHf3ITRaw== 182 | dependencies: 183 | "@babel/runtime" "^7.7.2" 184 | "@jimp/utils" "^0.16.1" 185 | 186 | "@jimp/plugin-print@^0.16.1": 187 | version "0.16.1" 188 | resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-0.16.1.tgz#66b803563f9d109825970714466e6ab9ae639ff6" 189 | integrity sha512-ceWgYN40jbN4cWRxixym+csyVymvrryuKBQ+zoIvN5iE6OyS+2d7Mn4zlNgumSczb9GGyZZESIgVcBDA1ezq0Q== 190 | dependencies: 191 | "@babel/runtime" "^7.7.2" 192 | "@jimp/utils" "^0.16.1" 193 | load-bmfont "^1.4.0" 194 | 195 | "@jimp/plugin-resize@^0.16.1": 196 | version "0.16.1" 197 | resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.16.1.tgz#65e39d848ed13ba2d6c6faf81d5d590396571d10" 198 | integrity sha512-u4JBLdRI7dargC04p2Ha24kofQBk3vhaf0q8FwSYgnCRwxfvh2RxvhJZk9H7Q91JZp6wgjz/SjvEAYjGCEgAwQ== 199 | dependencies: 200 | "@babel/runtime" "^7.7.2" 201 | "@jimp/utils" "^0.16.1" 202 | 203 | "@jimp/plugin-rotate@^0.16.1": 204 | version "0.16.1" 205 | resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-0.16.1.tgz#53fb5d51a4b3d05af9c91c2a8fffe5d7a1a47c8c" 206 | integrity sha512-ZUU415gDQ0VjYutmVgAYYxC9Og9ixu2jAGMCU54mSMfuIlmohYfwARQmI7h4QB84M76c9hVLdONWjuo+rip/zg== 207 | dependencies: 208 | "@babel/runtime" "^7.7.2" 209 | "@jimp/utils" "^0.16.1" 210 | 211 | "@jimp/plugin-scale@^0.16.1": 212 | version "0.16.1" 213 | resolved "https://registry.yarnpkg.com/@jimp/plugin-scale/-/plugin-scale-0.16.1.tgz#89f6ba59feed3429847ed226aebda33a240cc647" 214 | integrity sha512-jM2QlgThIDIc4rcyughD5O7sOYezxdafg/2Xtd1csfK3z6fba3asxDwthqPZAgitrLgiKBDp6XfzC07Y/CefUw== 215 | dependencies: 216 | "@babel/runtime" "^7.7.2" 217 | "@jimp/utils" "^0.16.1" 218 | 219 | "@jimp/plugin-shadow@^0.16.1": 220 | version "0.16.1" 221 | resolved "https://registry.yarnpkg.com/@jimp/plugin-shadow/-/plugin-shadow-0.16.1.tgz#a7af892a740febf41211e10a5467c3c5c521a04c" 222 | integrity sha512-MeD2Is17oKzXLnsphAa1sDstTu6nxscugxAEk3ji0GV1FohCvpHBcec0nAq6/czg4WzqfDts+fcPfC79qWmqrA== 223 | dependencies: 224 | "@babel/runtime" "^7.7.2" 225 | "@jimp/utils" "^0.16.1" 226 | 227 | "@jimp/plugin-threshold@^0.16.1": 228 | version "0.16.1" 229 | resolved "https://registry.yarnpkg.com/@jimp/plugin-threshold/-/plugin-threshold-0.16.1.tgz#34f3078f9965145b7ae26c53a32ad74b1195bbf5" 230 | integrity sha512-iGW8U/wiCSR0+6syrPioVGoSzQFt4Z91SsCRbgNKTAk7D+XQv6OI78jvvYg4o0c2FOlwGhqz147HZV5utoSLxA== 231 | dependencies: 232 | "@babel/runtime" "^7.7.2" 233 | "@jimp/utils" "^0.16.1" 234 | 235 | "@jimp/plugins@^0.16.1": 236 | version "0.16.1" 237 | resolved "https://registry.yarnpkg.com/@jimp/plugins/-/plugins-0.16.1.tgz#9f08544c97226d6460a16ced79f57e85bec3257b" 238 | integrity sha512-c+lCqa25b+4q6mJZSetlxhMoYuiltyS+ValLzdwK/47+aYsq+kcJNl+TuxIEKf59yr9+5rkbpsPkZHLF/V7FFA== 239 | dependencies: 240 | "@babel/runtime" "^7.7.2" 241 | "@jimp/plugin-blit" "^0.16.1" 242 | "@jimp/plugin-blur" "^0.16.1" 243 | "@jimp/plugin-circle" "^0.16.1" 244 | "@jimp/plugin-color" "^0.16.1" 245 | "@jimp/plugin-contain" "^0.16.1" 246 | "@jimp/plugin-cover" "^0.16.1" 247 | "@jimp/plugin-crop" "^0.16.1" 248 | "@jimp/plugin-displace" "^0.16.1" 249 | "@jimp/plugin-dither" "^0.16.1" 250 | "@jimp/plugin-fisheye" "^0.16.1" 251 | "@jimp/plugin-flip" "^0.16.1" 252 | "@jimp/plugin-gaussian" "^0.16.1" 253 | "@jimp/plugin-invert" "^0.16.1" 254 | "@jimp/plugin-mask" "^0.16.1" 255 | "@jimp/plugin-normalize" "^0.16.1" 256 | "@jimp/plugin-print" "^0.16.1" 257 | "@jimp/plugin-resize" "^0.16.1" 258 | "@jimp/plugin-rotate" "^0.16.1" 259 | "@jimp/plugin-scale" "^0.16.1" 260 | "@jimp/plugin-shadow" "^0.16.1" 261 | "@jimp/plugin-threshold" "^0.16.1" 262 | timm "^1.6.1" 263 | 264 | "@jimp/png@^0.16.1": 265 | version "0.16.1" 266 | resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.16.1.tgz#f24cfc31529900b13a2dd9d4fdb4460c1e4d814e" 267 | integrity sha512-iyWoCxEBTW0OUWWn6SveD4LePW89kO7ZOy5sCfYeDM/oTPLpR8iMIGvZpZUz1b8kvzFr27vPst4E5rJhGjwsdw== 268 | dependencies: 269 | "@babel/runtime" "^7.7.2" 270 | "@jimp/utils" "^0.16.1" 271 | pngjs "^3.3.3" 272 | 273 | "@jimp/tiff@^0.16.1": 274 | version "0.16.1" 275 | resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.16.1.tgz#0e8756695687d7574b6bc73efab0acd4260b7a12" 276 | integrity sha512-3K3+xpJS79RmSkAvFMgqY5dhSB+/sxhwTFA9f4AVHUK0oKW+u6r52Z1L0tMXHnpbAdR9EJ+xaAl2D4x19XShkQ== 277 | dependencies: 278 | "@babel/runtime" "^7.7.2" 279 | utif "^2.0.1" 280 | 281 | "@jimp/types@^0.16.1": 282 | version "0.16.1" 283 | resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.16.1.tgz#0dbab37b3202315c91010f16c31766d35a2322cc" 284 | integrity sha512-g1w/+NfWqiVW4CaXSJyD28JQqZtm2eyKMWPhBBDCJN9nLCN12/Az0WFF3JUAktzdsEC2KRN2AqB1a2oMZBNgSQ== 285 | dependencies: 286 | "@babel/runtime" "^7.7.2" 287 | "@jimp/bmp" "^0.16.1" 288 | "@jimp/gif" "^0.16.1" 289 | "@jimp/jpeg" "^0.16.1" 290 | "@jimp/png" "^0.16.1" 291 | "@jimp/tiff" "^0.16.1" 292 | timm "^1.6.1" 293 | 294 | "@jimp/utils@^0.16.1": 295 | version "0.16.1" 296 | resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.16.1.tgz#2f51e6f14ff8307c4aa83d5e1a277da14a9fe3f7" 297 | integrity sha512-8fULQjB0x4LzUSiSYG6ZtQl355sZjxbv8r9PPAuYHzS9sGiSHJQavNqK/nKnpDsVkU88/vRGcE7t3nMU0dEnVw== 298 | dependencies: 299 | "@babel/runtime" "^7.7.2" 300 | regenerator-runtime "^0.13.3" 301 | 302 | any-base@^1.1.0: 303 | version "1.1.0" 304 | resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe" 305 | integrity sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg== 306 | 307 | base64-js@^1.3.1: 308 | version "1.5.1" 309 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 310 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 311 | 312 | bmp-js@^0.1.0: 313 | version "0.1.0" 314 | resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233" 315 | integrity sha1-4Fpj95amwf8l9Hcex62twUjAcjM= 316 | 317 | buffer-equal@0.0.1: 318 | version "0.0.1" 319 | resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" 320 | integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs= 321 | 322 | buffer@^5.2.0: 323 | version "5.7.1" 324 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" 325 | integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== 326 | dependencies: 327 | base64-js "^1.3.1" 328 | ieee754 "^1.1.13" 329 | 330 | dom-walk@^0.1.0: 331 | version "0.1.2" 332 | resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" 333 | integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== 334 | 335 | exif-parser@^0.1.12: 336 | version "0.1.12" 337 | resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922" 338 | integrity sha1-WKnS1ywCwfbwKg70qRZicrd2CSI= 339 | 340 | file-type@^9.0.0: 341 | version "9.0.0" 342 | resolved "https://registry.yarnpkg.com/file-type/-/file-type-9.0.0.tgz#a68d5ad07f486414dfb2c8866f73161946714a18" 343 | integrity sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw== 344 | 345 | gifwrap@^0.9.2: 346 | version "0.9.2" 347 | resolved "https://registry.yarnpkg.com/gifwrap/-/gifwrap-0.9.2.tgz#348e286e67d7cf57942172e1e6f05a71cee78489" 348 | integrity sha512-fcIswrPaiCDAyO8xnWvHSZdWChjKXUanKKpAiWWJ/UTkEi/aYKn5+90e7DE820zbEaVR9CE2y4z9bzhQijZ0BA== 349 | dependencies: 350 | image-q "^1.1.1" 351 | omggif "^1.0.10" 352 | 353 | global@~4.4.0: 354 | version "4.4.0" 355 | resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" 356 | integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== 357 | dependencies: 358 | min-document "^2.19.0" 359 | process "^0.11.10" 360 | 361 | ieee754@^1.1.13: 362 | version "1.2.1" 363 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 364 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 365 | 366 | image-q@^1.1.1: 367 | version "1.1.1" 368 | resolved "https://registry.yarnpkg.com/image-q/-/image-q-1.1.1.tgz#fc84099664460b90ca862d9300b6bfbbbfbf8056" 369 | integrity sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY= 370 | 371 | is-function@^1.0.1: 372 | version "1.0.2" 373 | resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" 374 | integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== 375 | 376 | jimp@^0.16.1: 377 | version "0.16.1" 378 | resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.16.1.tgz#192f851a30e5ca11112a3d0aa53137659a78ca7a" 379 | integrity sha512-+EKVxbR36Td7Hfd23wKGIeEyHbxShZDX6L8uJkgVW3ESA9GiTEPK08tG1XI2r/0w5Ch0HyJF5kPqF9K7EmGjaw== 380 | dependencies: 381 | "@babel/runtime" "^7.7.2" 382 | "@jimp/custom" "^0.16.1" 383 | "@jimp/plugins" "^0.16.1" 384 | "@jimp/types" "^0.16.1" 385 | regenerator-runtime "^0.13.3" 386 | 387 | jpeg-js@0.4.2: 388 | version "0.4.2" 389 | resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.2.tgz#8b345b1ae4abde64c2da2fe67ea216a114ac279d" 390 | integrity sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw== 391 | 392 | load-bmfont@^1.3.1, load-bmfont@^1.4.0: 393 | version "1.4.1" 394 | resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.1.tgz#c0f5f4711a1e2ccff725a7b6078087ccfcddd3e9" 395 | integrity sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA== 396 | dependencies: 397 | buffer-equal "0.0.1" 398 | mime "^1.3.4" 399 | parse-bmfont-ascii "^1.0.3" 400 | parse-bmfont-binary "^1.0.5" 401 | parse-bmfont-xml "^1.1.4" 402 | phin "^2.9.1" 403 | xhr "^2.0.1" 404 | xtend "^4.0.0" 405 | 406 | mime@^1.3.4: 407 | version "1.6.0" 408 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 409 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 410 | 411 | min-document@^2.19.0: 412 | version "2.19.0" 413 | resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" 414 | integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= 415 | dependencies: 416 | dom-walk "^0.1.0" 417 | 418 | minimist@^1.2.5: 419 | version "1.2.5" 420 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 421 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 422 | 423 | mkdirp@^0.5.1: 424 | version "0.5.5" 425 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 426 | integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== 427 | dependencies: 428 | minimist "^1.2.5" 429 | 430 | omggif@^1.0.10, omggif@^1.0.9: 431 | version "1.0.10" 432 | resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" 433 | integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw== 434 | 435 | pako@^1.0.5: 436 | version "1.0.11" 437 | resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" 438 | integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== 439 | 440 | parse-bmfont-ascii@^1.0.3: 441 | version "1.0.6" 442 | resolved "https://registry.yarnpkg.com/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz#11ac3c3ff58f7c2020ab22769079108d4dfa0285" 443 | integrity sha1-Eaw8P/WPfCAgqyJ2kHkQjU36AoU= 444 | 445 | parse-bmfont-binary@^1.0.5: 446 | version "1.0.6" 447 | resolved "https://registry.yarnpkg.com/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz#d038b476d3e9dd9db1e11a0b0e53a22792b69006" 448 | integrity sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY= 449 | 450 | parse-bmfont-xml@^1.1.4: 451 | version "1.1.4" 452 | resolved "https://registry.yarnpkg.com/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz#015319797e3e12f9e739c4d513872cd2fa35f389" 453 | integrity sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ== 454 | dependencies: 455 | xml-parse-from-string "^1.0.0" 456 | xml2js "^0.4.5" 457 | 458 | parse-headers@^2.0.0: 459 | version "2.0.4" 460 | resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.4.tgz#9eaf2d02bed2d1eff494331ce3df36d7924760bf" 461 | integrity sha512-psZ9iZoCNFLrgRjZ1d8mn0h9WRqJwFxM9q3x7iUjN/YT2OksthDJ5TiPCu2F38kS4zutqfW+YdVVkBZZx3/1aw== 462 | 463 | phin@^2.9.1: 464 | version "2.9.3" 465 | resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c" 466 | integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA== 467 | 468 | pixelmatch@^4.0.2: 469 | version "4.0.2" 470 | resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854" 471 | integrity sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ= 472 | dependencies: 473 | pngjs "^3.0.0" 474 | 475 | pngjs@^3.0.0, pngjs@^3.3.3: 476 | version "3.4.0" 477 | resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" 478 | integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== 479 | 480 | process@^0.11.10: 481 | version "0.11.10" 482 | resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" 483 | integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= 484 | 485 | regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: 486 | version "0.13.9" 487 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" 488 | integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== 489 | 490 | sax@>=0.6.0: 491 | version "1.2.4" 492 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 493 | integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== 494 | 495 | timm@^1.6.1: 496 | version "1.7.1" 497 | resolved "https://registry.yarnpkg.com/timm/-/timm-1.7.1.tgz#96bab60c7d45b5a10a8a4d0f0117c6b7e5aff76f" 498 | integrity sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw== 499 | 500 | tinycolor2@^1.4.1: 501 | version "1.4.2" 502 | resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" 503 | integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== 504 | 505 | utif@^2.0.1: 506 | version "2.0.1" 507 | resolved "https://registry.yarnpkg.com/utif/-/utif-2.0.1.tgz#9e1582d9bbd20011a6588548ed3266298e711759" 508 | integrity sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg== 509 | dependencies: 510 | pako "^1.0.5" 511 | 512 | xhr@^2.0.1: 513 | version "2.6.0" 514 | resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d" 515 | integrity sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA== 516 | dependencies: 517 | global "~4.4.0" 518 | is-function "^1.0.1" 519 | parse-headers "^2.0.0" 520 | xtend "^4.0.0" 521 | 522 | xml-parse-from-string@^1.0.0: 523 | version "1.0.1" 524 | resolved "https://registry.yarnpkg.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz#a9029e929d3dbcded169f3c6e28238d95a5d5a28" 525 | integrity sha1-qQKekp09vN7RafPG4oI42VpdWig= 526 | 527 | xml2js@^0.4.5: 528 | version "0.4.23" 529 | resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" 530 | integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== 531 | dependencies: 532 | sax ">=0.6.0" 533 | xmlbuilder "~11.0.0" 534 | 535 | xmlbuilder@~11.0.0: 536 | version "11.0.1" 537 | resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" 538 | integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== 539 | 540 | xtend@^4.0.0: 541 | version "4.0.2" 542 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 543 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 544 | --------------------------------------------------------------------------------