main.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <script>
  2. const Fifo = require('fifo')
  3. const SCREEN_WIDTH = 256;
  4. const SCREEN_HEIGHT = 240;
  5. const BUFFER_FRAMES = 120;
  6. const SAMPLE_REDUCTION = 4;
  7. const MAX_BUFFER = 0; 1200;
  8. const WORKING_MEMORY = 0x800;
  9. const { cartesianToPolar:c2p, polarToCartesian:p2c, findMemory } = require('../common/util')
  10. window.findMemory = findMemory
  11. export default {
  12. data: () => ({
  13. gameState: {},
  14. gameStateString: '',
  15. memory: new Uint8Array(WORKING_MEMORY),
  16. watch: [
  17. 0x0086
  18. ]
  19. }),
  20. beforeDestroy() {
  21. console.log('beforeDestroy()')
  22. this.stop = true
  23. this.ws.close()
  24. },
  25. mounted() {
  26. console.log('mounted()')
  27. const buffer = new Fifo()
  28. const url = new URL('game', location.href)
  29. url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
  30. const connect = () => {
  31. this.ws = new WebSocket(url)
  32. this.ws.binaryType = 'arraybuffer'
  33. this.ws.addEventListener('message', msg => {
  34. if (typeof msg.data === 'string') {
  35. this.gameState = JSON.parse(msg.data)
  36. this.gameStateString = JSON.stringify(this.gameState.enemyVision, null, 2)
  37. .replace(/\{[^\{\}\[\]]*\}/g, x => x.replace(/\s+/g, ' '))
  38. .replace(/\[[^\{\}\[\]]*\]/g, x => x.replace(/\s+/g, ' '))
  39. } else {
  40. //buffer.push(msg.data)
  41. //if (buffer.length === MAX_BUFFER) buffer.shift()
  42. // if (!timer) {
  43. // timer = true
  44. this.renderFrame(msg.data)
  45. this.renderDebug(this.gameState)
  46. //}
  47. }
  48. })
  49. this.ws.addEventListener('close', () => {
  50. if (!this.stop) setTimeout(connect, 1000)
  51. })
  52. }
  53. connect()
  54. this.canvas = this.$refs.canvas
  55. this.context = this.canvas.getContext('2d')
  56. this.imageData = this.context.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
  57. this.memoryCanvas = this.$refs.memoryCanvas
  58. this.memoryContext = this.memoryCanvas.getContext('2d')
  59. this.memoryImageData = this.memoryContext.getImageData(0, 0, this.memoryCanvas.getAttribute('width'), this.memoryCanvas.getAttribute('height'))
  60. this.buf = new ArrayBuffer(this.imageData.data.length);
  61. // Get the canvas buffer in 8bit and 32bit
  62. this.buf8 = new Uint8ClampedArray(this.buf);
  63. this.buf32 = new Uint32Array(this.buf);
  64. this.timer = null
  65. this.keyMap = {
  66. a: /*BUTTON_B*/ 1,
  67. s: /*BUTTON_A*/ 0,
  68. '\\': /*BUTTON_SELECT*/ 2,
  69. Enter: /*BUTTON_START*/ 3,
  70. ArrowUp: /*BUTTON_UP*/ 4,
  71. ArrowDown: /*BUTTON_DOWN*/ 5,
  72. ArrowLeft: /*BUTTON_LEFT*/ 6,
  73. ArrowRight: /*BUTTON_RIGHT*/ 7
  74. }
  75. this.key = (ev, pressed) => {
  76. if (this.keyMap.hasOwnProperty(ev.key)) {
  77. const button = this.keyMap[ev.key]
  78. if (this.ws && this.ws.readyState === WebSocket.OPEN) {
  79. this.ws.send(JSON.stringify({
  80. type: 'button',
  81. button,
  82. pressed
  83. }))
  84. } else {
  85. console.warn('Not connected')
  86. }
  87. }
  88. }
  89. document.addEventListener('keydown', (ev) => this.key(ev, true))
  90. document.addEventListener('keyup', (ev) => this.key(ev, false))
  91. },
  92. methods: {
  93. renderDebug(data) {
  94. window.data = data
  95. this.context.save()
  96. // this.context.translate(8, 8)
  97. this.context.translate(data.x, data.y + 32)
  98. // this.context.beginPath()
  99. // this.context.fillStyle = '#080'
  100. // //this.context.fillRect(data.hitbox.x1, data.hitbox.y1, data.hitbox.x2 - data.hitbox.x1, data.hitbox.y2 - data.hitbox.y1)
  101. // this.context.arc(0, 0, 8, 0, 2 * Math.PI)
  102. // this.context.fill()
  103. // data.enemies.forEach(enemy => {
  104. // if (enemy && enemy.hitbox.y2 > enemy.hitbox.y1) {
  105. // this.context.beginPath()
  106. // this.context.fillStyle = '#800'
  107. // this.context.arc(enemy.x, enemy.y, 8, 0, 2 * Math.PI)
  108. // //this.context.fillRect(enemy.hitbox.x1, enemy.hitbox.y1, enemy.hitbox.x2 - enemy.hitbox.x1, enemy.hitbox.y2 - enemy.hitbox.y1)
  109. // this.context.fill()
  110. // }
  111. // })
  112. const renderVision = (collection, color) => {
  113. const arc = (Math.PI * 2 / data.slices)
  114. this.context.save()
  115. this.context.translate((data.hitbox.x2 - data.hitbox.x1) / 2, (data.hitbox.y2 - data.hitbox.y1) / -2)
  116. for (let i = 0; i < data.slices; i++) {
  117. const r = collection[i]
  118. const {x, y} = p2c({
  119. r: (1 - r) * 30,
  120. t: arc * i + arc / 2 - Math.PI
  121. })
  122. this.context.beginPath()
  123. this.context.fillStyle = color
  124. this.context.arc(x, y, 2, 0, 2 * Math.PI)
  125. //this.context.fillRect(enemy.hitbox.x1, enemy.hitbox.y1, enemy.hitbox.x2 - enemy.hitbox.x1, enemy.hitbox.y2 - enemy.hitbox.y1)
  126. this.context.fill()
  127. }
  128. this.context.restore()
  129. }
  130. renderVision(data.enemyVision, 'red')
  131. renderVision(data.solidVision, 'blue')
  132. renderVision(data.breakableVision, 'yellow')
  133. renderVision(data.magicVision, 'green')
  134. const renderHitboxes = (collection, color) => {
  135. this.context.strokeStyle = color
  136. this.context.beginPath()
  137. collection.forEach(entity => {
  138. if (entity) this.context.rect(entity.hitbox.x1, entity.hitbox.y1, entity.hitbox.x2 - entity.hitbox.x1, entity.hitbox.y2 - entity.hitbox.y1)
  139. })
  140. this.context.stroke()
  141. }
  142. renderHitboxes(this.gameState.blocks, 'blue')
  143. renderHitboxes(data.enemies, 'red')
  144. renderHitboxes([data], 'yellow')
  145. // this.gameState.blocks.forEach(({x, y, v}) => {
  146. // // v = v.toString(16).padStart(2)
  147. // this.context.rect(x, y, 16, 16)
  148. // for (let xo = -512; xo <= 512; xo += 512) {
  149. // const xo = 0
  150. // this.context.rect(x * 16 + 0.5 - this.gameState.levelX % 512 + xo, y * 16 + 0.5 - data.y, 16, 16)
  151. // this.context.fillStyle = 'black'
  152. // this.context.fillText(v, x * 16 + 0.5 - this.gameState.levelX % 512 + xo + 1, y * 16 + 0.5 - data.y + 10)
  153. // this.context.fillStyle = 'black'
  154. // this.context.fillText(v, x * 16 + 0.5 - this.gameState.levelX % 512 + xo - 1, y * 16 + 0.5 - data.y + 7)
  155. // this.context.fillStyle = 'black'
  156. // this.context.fillText(v, x * 16 + 0.5 - this.gameState.levelX % 512 + xo + 1, y * 16 + 0.5 - data.y + 7)
  157. // this.context.fillStyle = 'black'
  158. // this.context.fillText(v, x * 16 + 0.5 - this.gameState.levelX % 512 + xo - 1, y * 16 + 0.5 - data.y + 10)
  159. // this.context.fillStyle = '#aaaa00'
  160. // this.context.fillText(v, x * 16 + 0.5 - this.gameState.levelX % 512 + xo, y * 16 + 0.5 - data.y + 9)
  161. // }
  162. // })
  163. // this.context.stroke();
  164. // this.context.strokeStyle = null
  165. this.context.restore()
  166. },
  167. renderFrame(data) {
  168. //const data = buffer.shift()
  169. if (data) {
  170. const palette = new Uint32Array(data, 0, 256 * 4)
  171. const pixels = new Uint8Array(data, 256 * 4, SCREEN_WIDTH * SCREEN_HEIGHT)
  172. const memory = new Uint8Array(data, 256 * 4 + SCREEN_WIDTH * SCREEN_HEIGHT, WORKING_MEMORY)
  173. Object.assign(this.memory, memory)
  174. window.memory = this.memory
  175. for (let i = 0; i < pixels.length; i++) {
  176. this.buf32[i] = 0xff000000 | palette[pixels[i]]
  177. }
  178. this.imageData.data.set(this.buf8)
  179. for (let i = 32 * 40; i < memory.length; i++) {
  180. const m = memory[i]
  181. for (let x = 0; x < 4; x++) {
  182. this.memoryImageData.data[i * 4 + x - 32 * 40] = m
  183. }
  184. }
  185. this.context.putImageData(this.imageData, 0, 0)
  186. this.memoryContext.putImageData(this.memoryImageData, 0, 0)
  187. }
  188. //const ms = Math.max(-0.8 * buffer.length + 160, 0)
  189. //timer = setTimeout(renderFrame, ms)
  190. //requestAnimationFrame(renderFrame)
  191. /* https://www.calculator.net/slope-calculator.html?type=1&x11=0&y11=500&x12=100&y12=16&x=12&y=7
  192. l = buffer.length
  193. l = 0, sleep 500
  194. l = 100, sleep 1000/60
  195. l = 200, sleep more
  196. */
  197. }
  198. },
  199. destroyed() {
  200. console.log('destroyed()')
  201. if (this.ws) this.ws.close()
  202. }
  203. }
  204. </script>
  205. <template>
  206. <v-app>
  207. <v-content>
  208. <v-toolbar color="primary">
  209. <v-toolbar-side-icon />
  210. <v-toolbar-title>Super Mario Bros</v-toolbar-title>
  211. </v-toolbar>
  212. <v-container>
  213. <canvas style="zoom: 2;" ref="canvas" width="256" height="240" />
  214. <canvas style="zoom: 4;" ref="memoryCanvas" width="16" height="128" />
  215. <pre>{{gameStateString}}</pre>
  216. <ul>
  217. <!-- "0x0069", "0x0075", "0x0093", "0x0109", "0x0113", "0x0114", "0x0115", "0x0118", "0x0119", "0x0130", "0x0163", "0x0164", "0x0168", "0x0184", "0x0185", "0x0186", "0x0187", "0x0190", "0x0191", "0x0202", "0x0215", "0x0216", "0x0239", "0x0246", "0x0269", "0x0281", "0x0282", "0x0283", "0x0289", "0x0637", "0x0641", "0x0642", "0x0645", "0x0646", "0x0791", "0x0987", "0x0988", "0x0989", "0x0996", "0x0997", "0x1002", "0x1003", "0x1008", "0x1050", "0x1052", "0x1084", "0x1178", "0x1180", "0x1181", "0x1182", "0x1183", "0x1212", "0x1213", "0x1214", "0x1215", "0x1220", "0x1221", "0x1222", "0x1223", "0x1286", "0x1302", "0x1318", "0x1334", "0x1350", "0x1366", "0x1382", "0x1398", "0x1563", "0x1564", "0x1565", "0x1578", "0x1579", "0x1580", "0x1581", "0x1593", "0x1594", "0x1595", "0x1596", "0x1597", "0x1611", "0x1612", "0x1613", "0x1623", "0x1624", "0x1627", "0x1628", "0x1629", "0x1636", "0x1638", "0x1639", "0x1640", "0x1643", "0x1653", "0x1654", "0x1655", "0x1656", "0x1659", "0x1675", "0x1676", "0x1677" -->
  218. <li> x, y: {{gameState.x}}, {{gameState.y}}</li>
  219. <li v-for="(key, index) in watch" :key="index">
  220. {{key.toString(16)}}: {{memory[key]}}
  221. </li>
  222. <li>0x006d (Level x): {{gameState.levelX}}</li>
  223. <li>0x0491 (Enemy collision): {{memory[0x0491]}}</li>
  224. <li>0x04AC: {{memory[0x04AC]}}</li>
  225. <li>0x04AC: {{memory[0x04AC]}}</li>
  226. <li>0x0750: {{memory[0x0750]}}</li>
  227. </ul>
  228. <!-- <div style="position: relative;">
  229. <div v-for="block in gameState.blocks"
  230. :key="gameState.blocks.indexOf(block)"
  231. :style="{
  232. display: 'block',
  233. width: '4px',
  234. height: '4px',
  235. left: (block.x * 4) + 'px',
  236. top: (block.y * 4) + 'px',
  237. position: 'absolute',
  238. backgroundColor: 'brown'
  239. }"
  240. >
  241. </div>
  242. </div> -->
  243. </v-container>
  244. </v-content>
  245. </v-app>
  246. </template>
  247. <style scoped>
  248. canvas {
  249. image-rendering: pixelated;
  250. }
  251. </style>