{
   "labels" : [
      "game",
      "snake",
      "arcade"
   ],
   "is_private" : null,
   "id" : "1-5eR",
   "code" : "(\r\n//cmd-enter to run, esc to close\r\n\r\nvar height=20, width=30,\r\nbody = (1..6),\r\nsize = body.size,\r\ngrow_inc = 2, //get 1 food = +2 size\r\nframerate = 16,\r\ndir = \\east,\r\n\r\n//keycodes for left/right/down/up\r\nkeycodes = Platform.case(\r\n\t\\osx, {[123,124,125,126]},\r\n\t\\linux, {[65361,65363,65364,65362]},\r\n\t\\windows, {[37,39,40,38]}\r\n);\r\n\r\n//snake + board are represented as 2D array,\r\n//0 = empty space\r\n//positive integers = snake body, value represents\r\n//number of frames until snake segment disappears\r\n//e.g. 6-segment snake heading east looks like:\r\n// [0, 0, 0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0]\r\nvar board = (0!(height*width)).clump(width);\r\n\r\n//current position of snake \"head\"\r\nvar pos = round(height/2) @ (size + 1);\r\n\r\n//amount/direction to shift on each frame\r\nvar shift = 0 @ 1;\r\n\r\n//for creating snake food\r\nvar foodpos, foodcolor, tryfoodpos;\r\n\r\n//gui\r\nvar win, u, deadbox;\r\n\r\n//functions that handle snake crash and food generation\r\nvar crash, makefood;\r\n\r\nWindow.closeAll;\r\n\r\nmakefood = {\r\n\r\n\t//colorful snacks\r\n\tfoodcolor = Color.rand(0.25,1.0);\r\n\tfoodpos = nil;\r\n\r\n\t//make sure food isn't placed on snake body\r\n\twhile(\r\n\t\t{foodpos == nil},\r\n\t\t{\r\n\t\t\ttryfoodpos = rand(height) @ rand(width);\r\n\t\t\tif(\r\n\t\t\t\t//food generation only successful if blank space is chosen\r\n\t\t\t\tboard[tryfoodpos.x][tryfoodpos.y] == 0,\r\n\t\t\t\t{foodpos = tryfoodpos.copy}\r\n\t\t\t);\r\n\t\t}\r\n\t);\r\n};\r\n\r\nwin = Window.new(\"snake\", Rect(\r\n\tWindow.screenBounds.width/2-(width*10),\r\n\tWindow.screenBounds.height/2-(height*10),\r\n\twidth*20,height*20\r\n), false, false)\r\n.alwaysOnTop_(true);\r\n\r\nu = UserView(win, win.view.bounds)\r\n.background_(Color.gray(0.1));\r\n\r\n//game over & score box, only visible when you crash\r\n//(score = snake body size)\r\ndeadbox = StaticText(win, Rect(\r\n\twin.bounds.width/2 - 50,\r\n\twin.bounds.height/2 - 20,\r\n\t100, 40\r\n))\r\n.visible_(false)\r\n.background_(Color.red(0.7))\r\n.stringColor_(Color.gray(0.8))\r\n.align_(\\center);\r\n\r\n//when crash, stop animation and display game over & score\r\ncrash = {\r\n\tu.animate_(false);\r\n\tdeadbox\r\n\t.string_(\"you died\\nscore: \"++size)\r\n\t.visible_(true);\r\n};\r\n\r\n//place snake body integers on 2D array\r\nbody.do({\r\n\targ n,i;\r\n\tboard[round(height/2)].put(2+i,n);\r\n});\r\n\r\n//generate a food location\r\nmakefood.();\r\n\r\n//game animation handled by userview drawfunc\r\nu.drawFunc_({\r\n\r\n\t//decrement board, lower bound at zero\r\n\tboard = (board - 1).max(0);\r\n\r\n\t//adjust shift based on movement direction\r\n\tcase\r\n\t{dir == \\east} {shift = 0 @ 1}\r\n\t{dir == \\west} {shift = 0 @ -1}\r\n\t{dir == \\north} {shift = -1 @ 0}\r\n\t{dir == \\south} {shift = 1 @ 0}\r\n\t{true} {nil};\r\n\r\n\t//new target position for snake head\r\n\tpos = pos + shift;\r\n\r\n\tif(\r\n\t\t//check for boundary collision, crash if so\r\n\t\t(pos.y >= width) || (pos.y < 0) ||\r\n\t\t(pos.x >= height) || (pos.x < 0),\r\n\r\n\t\tcrash,\r\n\r\n\t\t{\r\n\t\t\t//if no boundary crash, check for self-collision, crash if so\r\n\t\t\tif(\r\n\t\t\t\t(board[pos.x][pos.y] > 0),\r\n\r\n\t\t\t\tcrash,\r\n\r\n\t\t\t\t//if no collision, place snake head on board\r\n\t\t\t\t{board[pos.x][pos.y] = size}\r\n\t\t\t);\r\n\t\t}\r\n\t);\r\n\r\n\t//check if food was acquired\r\n\tif(\r\n\t\tpos == foodpos,\r\n\t\t{\r\n\t\t\t//get bigger\r\n\t\t\tsize = size + grow_inc;\r\n\t\t\tboard = board.deepCollect(2, {\r\n\t\t\t\targ n;\r\n\t\t\t\tif (n!=0, {n + grow_inc}, {0})\r\n\t\t\t});\r\n\r\n\t\t\t//generate new food\r\n\t\t\tmakefood.();\r\n\t\t}\r\n\t);\r\n\r\n\t//draw next frame\r\n\tboard.do({\r\n\t\targ row, j;\r\n\t\trow.do({\r\n\t\t\targ val, i;\r\n\t\t\tPen.fillColor_(Color.gray(\r\n\t\t\t\t//positive numbers are light gray (snake body)\r\n\t\t\t\t//0s are dark grey (game board background)\r\n\t\t\t\t(val>0).asInteger*0.6+0.1\r\n\t\t\t));\r\n\t\t\tPen.addRect(Rect(i*20, j*20, 20, 20));\r\n\t\t\tPen.fill;\r\n\t\t});\r\n\t});\r\n\r\n\t//draw food\r\n\tPen.fillColor_(foodcolor);\r\n\tPen.addRect(Rect(foodpos.y*20,foodpos.x*20, 20, 20));\r\n\tPen.fill;\r\n});\r\n\r\n//userview updates snake direction in response to arrow keys\r\nu.keyDownAction_({\r\n\targ view, char, mod, uni, keycode;\r\n\tcase\r\n\t{keycode == keycodes[0]} {dir = \\west}\r\n\t{keycode == keycodes[1]} {dir = \\east}\r\n\t{keycode == keycodes[2]} {dir = \\south}\r\n\t{keycode == keycodes[3]} {dir = \\north}\r\n\t{uni == 27} {u.animate_(false); win.close}\r\n\t{true} {nil};\r\n});\r\n\r\n//set framerate and start game\r\nu.frameRate_(framerate);\r\nu.animate_(true);\r\nwin.front;\r\n)",
   "name" : "snake",
   "author" : "eli.fieldsteel",
   "ancestor_list" : [],
   "description" : "a SuperCollider recreation of the the arcade game \"snake\""
}
