game.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /*
  2. * Part of Snek vs Snacc webapp.
  3. * Released under AGPLv3. Copyright (C) NetherEran 2020
  4. * */
  5. var field_width = 38
  6. var field_height = 18
  7. var pixel_width = 20
  8. var wpixels = field_width * pixel_width
  9. var hpixels = field_height * pixel_width
  10. function isInsideMap(point) {
  11. return point.x >= 0 &&
  12. point.x < field_width &&
  13. point.y >= 0 &&
  14. point.y < field_height
  15. }
  16. var canvas = document.getElementById("game_field")
  17. var context = canvas.getContext("2d")
  18. function Point(x, y) {
  19. this.x = x
  20. this.y = y
  21. this.add = function(pt) {
  22. return new Point(this.x + pt.x, this.y + pt.y)
  23. }
  24. this.equals = function(pt) {
  25. return (this.x == pt.x && this.y == pt.y)
  26. }
  27. this.distance = function(pt) {
  28. return Math.sqrt((this.x - pt.x) ** 2 + (this.y - pt.y) ** 2)
  29. }
  30. }
  31. var player
  32. var snacc
  33. var points = 0
  34. var game_going = false
  35. var eatSnacc
  36. var setLost
  37. function Player(x, y, length) {
  38. this.positions = new Array()
  39. this.length = length
  40. for (var i = 0; i < length; i++) {
  41. this.positions[i] = new Point(x + i, y)
  42. }
  43. this.head_ptr = this.length - 1
  44. this.next_direction = new Point(1, 0)
  45. this.direction = this.next_direction
  46. this.move = function() {
  47. let oldhead = this.positions[this.head_ptr]
  48. this.head_ptr = (this.head_ptr + 1) % this.length
  49. this.direction = this.next_direction
  50. let newhead = oldhead.add(this.direction)
  51. if(newhead.equals(snacc.position)) {
  52. eatSnacc()
  53. }
  54. if (!isInsideMap(newhead)) {
  55. setLost()
  56. }
  57. for (var i = 0; i < this.length; i++) {
  58. if (i == this.head_ptr) {
  59. continue
  60. }
  61. if (this.positions[i].equals(newhead)) {
  62. setLost()
  63. }
  64. }
  65. this.positions[this.head_ptr] = newhead
  66. }
  67. }
  68. document.addEventListener('keydown', function(event) {
  69. if (!player) {
  70. return
  71. }
  72. switch(event.keyCode) {
  73. case 37: //LEFT
  74. case 65:
  75. if (player.direction.x != 1) {
  76. player.next_direction = new Point(-1, 0)
  77. }
  78. break
  79. case 38: //UP
  80. case 87:
  81. if (player.direction.y != 1) {
  82. player.next_direction = new Point(0, -1)
  83. }
  84. break
  85. case 39: //RIGHT
  86. case 68:
  87. if (player.direction.x != -1) {
  88. player.next_direction = new Point(1, 0)
  89. }
  90. break
  91. case 40: //DOWN
  92. case 83:
  93. if (player.direction.y != -1) {
  94. player.next_direction = new Point(0, 1)
  95. }
  96. break
  97. default:
  98. break
  99. }
  100. });
  101. function Snacc(x, y, tier, direction) {
  102. this.position = new Point(x, y)
  103. this.tier = tier
  104. this.direction = direction
  105. this.move_tick = 0
  106. this.move = function() {
  107. //bounce
  108. if ((this.position.x + 1 == field_width && this.direction.x > 0) ||
  109. (this.position.x == 0 && this.direction.x < 0)) {
  110. this.direction.x = -this.direction.x
  111. }
  112. if ((this.position.y + 1 == field_height && this.direction.y > 0) ||
  113. (this.position.y == 0 && this.direction.y < 0)) {
  114. this.direction.y = -this.direction.y
  115. }
  116. //move
  117. this.position = this.position.add(this.direction)
  118. //check for collision with player
  119. for (var i = 0; i < player.length; i++) {
  120. if (player.positions[i].equals(this.position)) {
  121. if (i == player.head_ptr) {
  122. eatSnacc()
  123. } else {
  124. setLost()
  125. }
  126. }
  127. }
  128. if (this.direction == 2) {
  129. //alert()
  130. }
  131. }
  132. this.tick = function() {
  133. this.move_tick += this.tier ** 2
  134. if (this.move_tick > 17) {
  135. this.move()
  136. this.move_tick = 0
  137. }
  138. }
  139. }
  140. function addSnacc(max_value) {
  141. let tier = Math.floor(Math.random() * max_value) + 1
  142. let direction = Math.floor(Math.random() * 4)
  143. switch(direction) {
  144. case 0:
  145. direction = new Point(1, 1)
  146. break
  147. case 1:
  148. direction = new Point(-1, 1)
  149. break
  150. case 2:
  151. direction = new Point(-1, -1)
  152. break
  153. case 3:
  154. direction = new Point(1, -1)
  155. break
  156. }
  157. //find a point on the field that's far enough from the player to
  158. //not kill them right away
  159. let playerhead = player.positions[player.head_ptr]
  160. let spawnpoint = new Point(0, 0)
  161. do {
  162. spawnpoint.x = Math.floor(Math.random() * field_width)
  163. spawnpoint.y = Math.floor(Math.random() * field_height)
  164. } while (spawnpoint.distance(playerhead) < 13 + tier)
  165. snacc = new Snacc(spawnpoint.x, spawnpoint.y, tier, direction)
  166. }
  167. eatSnacc = function() {
  168. points += snacc.tier ** 2
  169. let maxval = Math.log(points) * 2 + 1
  170. addSnacc(maxval > 4 ? 4 : maxval)
  171. }
  172. setLost = function() {
  173. game_going = false
  174. }
  175. function drawPixel(point) {
  176. var posx = point.x * pixel_width
  177. var posy = point.y * pixel_width
  178. context.fillRect(posx, posy, pixel_width, pixel_width)
  179. }
  180. function getSnaccColor(tier) {
  181. switch(tier) {
  182. case 1:
  183. return "magenta"
  184. case 2:
  185. return "red"
  186. case 3:
  187. return "blue"
  188. case 4:
  189. return "yellow"
  190. }
  191. }
  192. //Graphics stuff
  193. function redrawField() {
  194. context.clearRect(0, 0, canvas.width, canvas.height)
  195. let snacc_color = getSnaccColor(snacc.tier)
  196. context.fillStyle = snacc_color
  197. context.strokeStyle = snacc_color
  198. drawPixel(snacc.position)
  199. context.fillStyle = "cyan"
  200. context.strokeStyle = "cyan"
  201. for (var i = 0; i < player.length; i++) {
  202. drawPixel(player.positions[i])
  203. }
  204. }
  205. function drawGameOverScreen(points) {
  206. context.clearRect(0, 0, canvas.width, canvas.height)
  207. context.fillStyle = "lightgrey"
  208. context.strokeStyle = "lightgrey"
  209. context.font = "15pt a"
  210. context.fillText("Game Over", 320, 200)
  211. context.fillText(points + " Points", 320, 230)
  212. }
  213. //Game loop stuff
  214. var now, dt, last = timestamp();
  215. function timestamp() {
  216. return window.performance && window.performance.now ? window.performance.now() : new Date().getTime();
  217. }
  218. var player_tick = 0
  219. var snacc_tick = 0
  220. function tick() {
  221. now = timestamp()
  222. dtime = (now - last) / 1000 //in seconds
  223. last = now
  224. let redraw = false
  225. player_tick += dtime
  226. if (player_tick >= 0.15) {
  227. player.move()
  228. redraw = true
  229. player_tick -= 0.15
  230. }
  231. snacc_tick += dtime
  232. if (snacc_tick >= 0.05) {
  233. snacc.tick()
  234. redraw = true
  235. snacc_tick -= 0.05
  236. }
  237. if (game_going) {
  238. if (redraw) {
  239. redrawField()
  240. }
  241. requestAnimationFrame(tick);
  242. } else {
  243. drawGameOverScreen(points)
  244. }
  245. }
  246. function start() {
  247. //replace start button with restart button
  248. var startbtn = document.getElementById("start_button")
  249. startbtn.innerHTML = "RESTART"
  250. //start game
  251. player = new Player(Math.floor(field_width / 2), Math.floor(field_height / 2), 6)
  252. addSnacc(1)
  253. points = 0
  254. player_tick = 0
  255. snacc_tick = 0
  256. now = timestamp()
  257. last = now
  258. game_going = true
  259. redrawField()
  260. requestAnimationFrame(tick);
  261. }