├── bitmaps ├── awake.png ├── down1.png ├── down2.png ├── dtogi1.png ├── dtogi2.png ├── dwleft1.png ├── dwleft2.png ├── dwright1.png ├── dwright2.png ├── jare2.png ├── kaki1.png ├── kaki2.png ├── left1.png ├── left2.png ├── ltogi1.png ├── ltogi2.png ├── mati2.png ├── mati3.png ├── right1.png ├── right2.png ├── rtogi1.png ├── rtogi2.png ├── sleep1.png ├── sleep2.png ├── up1.png ├── up2.png ├── upleft1.png ├── upleft2.png ├── upright1.png ├── upright2.png ├── utogi1.png └── utogi2.png ├── index.html ├── jneko.js └── readme.md /bitmaps/awake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/awake.png -------------------------------------------------------------------------------- /bitmaps/down1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/down1.png -------------------------------------------------------------------------------- /bitmaps/down2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/down2.png -------------------------------------------------------------------------------- /bitmaps/dtogi1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/dtogi1.png -------------------------------------------------------------------------------- /bitmaps/dtogi2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/dtogi2.png -------------------------------------------------------------------------------- /bitmaps/dwleft1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/dwleft1.png -------------------------------------------------------------------------------- /bitmaps/dwleft2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/dwleft2.png -------------------------------------------------------------------------------- /bitmaps/dwright1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/dwright1.png -------------------------------------------------------------------------------- /bitmaps/dwright2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/dwright2.png -------------------------------------------------------------------------------- /bitmaps/jare2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/jare2.png -------------------------------------------------------------------------------- /bitmaps/kaki1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/kaki1.png -------------------------------------------------------------------------------- /bitmaps/kaki2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/kaki2.png -------------------------------------------------------------------------------- /bitmaps/left1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/left1.png -------------------------------------------------------------------------------- /bitmaps/left2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/left2.png -------------------------------------------------------------------------------- /bitmaps/ltogi1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/ltogi1.png -------------------------------------------------------------------------------- /bitmaps/ltogi2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/ltogi2.png -------------------------------------------------------------------------------- /bitmaps/mati2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/mati2.png -------------------------------------------------------------------------------- /bitmaps/mati3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/mati3.png -------------------------------------------------------------------------------- /bitmaps/right1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/right1.png -------------------------------------------------------------------------------- /bitmaps/right2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/right2.png -------------------------------------------------------------------------------- /bitmaps/rtogi1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/rtogi1.png -------------------------------------------------------------------------------- /bitmaps/rtogi2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/rtogi2.png -------------------------------------------------------------------------------- /bitmaps/sleep1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/sleep1.png -------------------------------------------------------------------------------- /bitmaps/sleep2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/sleep2.png -------------------------------------------------------------------------------- /bitmaps/up1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/up1.png -------------------------------------------------------------------------------- /bitmaps/up2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/up2.png -------------------------------------------------------------------------------- /bitmaps/upleft1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/upleft1.png -------------------------------------------------------------------------------- /bitmaps/upleft2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/upleft2.png -------------------------------------------------------------------------------- /bitmaps/upright1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/upright1.png -------------------------------------------------------------------------------- /bitmaps/upright2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/upright2.png -------------------------------------------------------------------------------- /bitmaps/utogi1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/utogi1.png -------------------------------------------------------------------------------- /bitmaps/utogi2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evert/jneko/f6f872e4d522b14be7d53007880c7bd838d949c3/bitmaps/utogi2.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jneko 5 | 6 | 7 | 8 | 9 | 46 | 47 | 48 | 49 |
50 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /jneko.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const imageNames = [ 4 | 'awake', 5 | // 'down1', 6 | // 'down2', 7 | // 'dtogi1', 8 | // 'dtogi2', 9 | // 'dwleft1', 10 | // 'dwleft2', 11 | 'jare2', 12 | 'kaki1', 13 | 'kaki2', 14 | // 'left1', 15 | // 'left2', 16 | // 'ltogi1', 17 | // 'ltog2', 18 | 'mati2', 19 | 'mati3', 20 | // 'right1', 21 | // 'right2', 22 | // 'rtogi1', 23 | // 'rtogi2', 24 | 'sleep1', 25 | 'sleep2', 26 | // 'up1', 27 | // 'up2', 28 | // 'upleft1', 29 | // 'upleft2', 30 | // 'upright1', 31 | // 'upright2', 32 | // 'utogi1', 33 | // 'utogi2', 34 | ]; 35 | const nekoSize = 64; 36 | const images = Object.fromEntries(imageNames.map(name => { 37 | const image = new Image(nekoSize, nekoSize); 38 | image.src = 'bitmaps/' + name + '.png'; 39 | return [name, image] 40 | })); 41 | 42 | 43 | 44 | function main() { 45 | 46 | const nekoDiv = document.getElementById('jneko'); 47 | const neko = new Neko(nekoDiv); 48 | 49 | } 50 | 51 | window.addEventListener('DOMContentLoaded', () => main()); 52 | 53 | const stateMachine = { 54 | 55 | // Name of the state 56 | sleep: { 57 | 58 | // Which images are used for the state 59 | image: ['sleep1', 'sleep2'], 60 | 61 | // How quickly we loop through these images 62 | imageInterval: 1, 63 | 64 | // What state does this go to when clicked 65 | click: 'awake' 66 | }, 67 | awake: { 68 | image: 'awake', 69 | 70 | // We automaticall transition to this state 71 | nextState: 'normal', 72 | 73 | // How long it takes to transition 74 | nextStateDelay: 2.5, 75 | }, 76 | normal: { 77 | image: 'mati2', 78 | 79 | // If there's multiple nextState values, a random one is picked. 80 | // You make make 1 state more likely by addding it multiple times 81 | // to the array 82 | nextState: ['normal', 'normal', 'normal', 'tilt', 'scratch', 'yawn'], 83 | nextStateDelay: 1.5, 84 | }, 85 | tilt: { 86 | image: 'jare2', 87 | nextState: 'normal', 88 | nextStateDelay: 1, 89 | }, 90 | yawn: { 91 | image: 'mati3', 92 | nextState: ['normal', 'normal', 'sleep'], 93 | nextStateDelay: 1, 94 | }, 95 | scratch: { 96 | image: ['kaki1', 'kaki2'], 97 | imageInterval: 0.1, 98 | nextState: 'normal', 99 | nextStateDelay: 3, 100 | } 101 | }; 102 | 103 | class Neko { 104 | 105 | constructor(elem) { 106 | 107 | // The HTML element that hosts neko 108 | this.elem = elem; 109 | this.stateMachine = stateMachine; 110 | 111 | // Creating a new element 112 | this.imgElem = new Image(nekoSize, nekoSize); 113 | 114 | elem.appendChild(this.imgElem); 115 | this.imgElem.addEventListener('click', () => this.onClick()); 116 | 117 | this.setState('sleep'); 118 | 119 | } 120 | 121 | /** 122 | * This property is used to keep track of states with multiple frames 123 | */ 124 | #animationIndex = 0; 125 | 126 | renderImage() { 127 | 128 | let name = this.stateMachine[this.#state].image; 129 | 130 | this.#animationIndex++; 131 | if (Array.isArray(name)) { 132 | name = name[this.#animationIndex % name.length]; 133 | } 134 | console.log('Rendering %s', name); 135 | this.imgElem.src = images[name].src; 136 | } 137 | 138 | #state = null; 139 | #nextStateTimeout = null; 140 | #imageCycleInterval = null; 141 | 142 | setState(stateName) { 143 | 144 | clearTimeout(this.nextStateTimeout); 145 | clearInterval(this.imageCycleInterval); 146 | 147 | if (Array.isArray(stateName)) { 148 | // If stateName was supplied as an array of strings, we'll randomly 149 | // pick a new state. 150 | stateName = stateName[Math.floor(Math.random()*(stateName.length))]; 151 | } 152 | if (!this.stateMachine[stateName]) { 153 | throw new Error('Uknown state: ' + stateName); 154 | } 155 | this.#state = stateName; 156 | const stateData = this.stateMachine[this.#state]; 157 | 158 | // If there was a nextState, we automatically transition there after 159 | // a delay. 160 | if (stateData.nextState) { 161 | this.nextStateTimeout = setTimeout( 162 | () => this.setState(stateData.nextState), 163 | stateData.nextStateDelay * 1000 164 | ); 165 | } 166 | 167 | // This block is responsible for cycling through multiple images 168 | // of the current state. 169 | if (stateData.imageInterval) { 170 | 171 | this.imageCycleInterval = setInterval( 172 | () => this.renderImage(), 173 | stateData.imageInterval*1000 174 | ); 175 | 176 | } 177 | this.renderImage(); 178 | } 179 | onClick() { 180 | const stateData = this.stateMachine[this.#state]; 181 | if (stateData.click) { 182 | // If the current state had a 'click' property, we'll transition 183 | // to that state, otherwise it's ignored. 184 | this.setState(stateData.click); 185 | } 186 | 187 | } 188 | 189 | } 190 | 191 | 192 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | jneko - Javascript implementation of Neko 2 | ========================================= 3 | 4 | Made for this blog post: 5 | 6 | Neko is a desktop pet originall written by Naoshi Watanabe for the NEC PC-8901. 7 | Kenji Gotoh created a Macintosh version in 1989, which has formed the basis of 8 | many ports. 9 | 10 | Over 30 years later, here's a simple Javascript version. This version doesn't 11 | follow the mouse pointer around, and stays asleep unless awakened. 12 | 13 | All the art is taken from Oneko, which is public domain. Thank you for Tatsuya 14 | Kato for creating oneko and Masayuki Koba for creating xneko. 15 | 16 | --------------------------------------------------------------------------------