import java.awt._ import java.awt.event._ import java.awt.geom._ import java.io._ import java.util.Scanner import javax.imageio.ImageIO import javax.swing._ import scala.collection.mutable.ArrayBuffer import scala.io.Source import math._ object SnakePic { val dirs = Array("r","l","d","u","b","f","") def dir(from : (Int,Int,Int), to : (Int,Int,Int)) = { if(from._1 < to._1) 0 else if(from._1 > to._1) 1 else if(from._2 < to._2) 2 else if(from._2 > to._2) 3 else if(from._3 > to._3) 4 else if(from._3 < to._3) 5 else 6 } } class SnakePic extends JComponent { val apple_sprites = for(col <- Array("red","green","shadow")) yield getImg("apple_%s.png".format(col)) val wall_sprites = for(col <- Array("1","0","_shadow")) yield getImg("wall%s.png".format(col)) val snake_sprites = for(col <- Array("red","green","shadow")) yield for (d1 <- 0 to 6) yield for(d2 <- 0 to 6) yield getImg("snake_%s_%s.png".format(Array(d1,d2).distinct.sorted.map(SnakePic.dirs.apply).mkString(""),col)) val dir_sprites = for(col <- Array("r","g")) yield for(d <- Array("right","left","down","up","in","out")) yield getImg("%s_%s.png".format(col,d)) def getImg(s : String) = { ImageIO.read(getClass().getResource(s)) } def computeBoundaries() : (Int,Int,Int,Int) = { val width = 48*Snake.game.width+48 val height = 48*Snake.game.height+48 val h = getHeight()-10 val w = getWidth()-10 val diff = h*width-w*height val hcut = max(diff,0)/(width*2) val wcut = max(-diff,0)/(height*2) (5+wcut,5+hcut,5+w-wcut,5+h-hcut) } def computeSquare(l : Int, t : Int, r : Int, b : Int, x : Int, y : Int, z : Int, width : Int, height : Int) : (Int, Int, Int, Int) = { val xmin = (2*x*r+(width-x)*l+z*width)/(2*width+1) val xmax = (2*(x+1)*r+(width-x-1)*l+z*width)/(2*width+1) val ymin = (2*y*b+(height-y)*t+z*height)/(2*height+1) val ymax = (2*(y+1)*b+(height-y-1)*t+z*height)/(2*height+1) (xmin,ymin,xmax,ymax) } def snakeDir(col : Int, space : Int) = { val path = Snake.game.path(col) val pos = path(space) val d1 = if(space==0) if(Snake.game.stage(col)==2) SnakePic.dir(pos,path(path.size-1)) else 6 else SnakePic.dir(pos,path(space-1)) val d2 = if(space==path.size-1) if(Snake.game.stage(col)==2) SnakePic.dir(pos,path(0)) else 6 else SnakePic.dir(pos,path(space+1)) (d1,d2) } def snakeSprite(col : Int, space : Int) = { val (d1,d2) = snakeDir(col,space) snake_sprites(col)(d1)(d2) } def snakeShadow(col : Int, space : Int) = { val (d1,d2) = snakeDir(col,space) snake_sprites(2)(d1)(d2) } def atGround(p : (Int,Int,Int)) = (p._1,p._2,0) def screenDepth(p : (Int,Int,Int)) = p._1+p._2+6*p._3 def shadowLoc(p : (Int,Int,Int)) = (p._1,p._2,-1) override def paint(g : Graphics) { val g2 = g.asInstanceOf[Graphics2D] g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BICUBIC) val at_orig = g2.getTransform() val squares = Snake.game.squares val width = Snake.game.width val height = Snake.game.height val (l, t, r, b) = computeBoundaries() g.setColor(Color.BLACK) g.fillRect(0,0,getWidth(),getHeight()) g.setColor(Color.WHITE) g.fillRect(l,t,r-l,b-t) g2.transform(new AffineTransform((r-l)/(48*width+48.),0.,0.,(b-t)/(48*height+48.),l,t)) val apples = for(x <- 0 until width; y <- 0 until height; z <- 0 until 2 if (3 to 4).contains(squares(z)(y)(x))) yield ((x,y,z),apple_sprites(squares(z)(y)(x)-3)) val walls = for(x <- 0 until width; y <- 0 until height; z <- 0 until 2 if squares(z)(y)(x)==5) yield ((x,y,z),wall_sprites(z)) val snakes = for(i <- 0 until 2; j <- 0 to Snake.game.path(i).size-1) yield (Snake.game.path(i)(j),snakeSprite(i,j)) val apple_shadows = for(x <- 0 until width; y <- 0 until height; z <- 0 until 2 if (3 to 4).contains(squares(z)(y)(x))) yield ((x,y,-1),apple_sprites(2)) val wall_shadows = for(x <- 0 until width; y <- 0 until height; z <- 0 until 2 if squares(z)(y)(x)==5) yield ((x,y,-1),wall_sprites(2)) val snake_shadows = for(i <- 0 until 2; j <- 0 to Snake.game.path(i).size-1) yield (shadowLoc(Snake.game.path(i)(j)),snakeShadow(i,j)) val sprites = (apples++snakes++walls++apple_shadows++snake_shadows++wall_shadows).sortBy(p => screenDepth(p._1)) for(((x,y,z),img) <- sprites) { //println(z) g.drawImage(img,48*x+16*(1-z),48*y+16*(1-z),img.getWidth(),img.getHeight(),null) } val (x1,y1,z1) = Snake.game.path(0)(Snake.game.path(0).size-1) val (dx1,dy1,dz1) = Snake.game.dir(0) g.drawImage(dir_sprites(0)(SnakePic.dir((0,0,0),(dx1,dy1,dz1))),48*(x1+dx1)+16*(1-z1-dz1)+16,48*(y1+dy1)+16*(1-z1-dz1)+16,32,32,null) val (x2,y2,z2) = Snake.game.path(1)(Snake.game.path(1).size-1) val (dx2,dy2,dz2) = Snake.game.dir(1) g.drawImage(dir_sprites(1)(SnakePic.dir((0,0,0),(dx2,dy2,dz2))),48*(x2+dx2)+16*(1-z2-dz2)+16,48*(y2+dy2)+16*(1-z2-dz2)+16,32,32,null) g2.setTransform(at_orig) } } object SnakeGame { // levels start with 1, so pad with null val dimensions = Array(null,(20,10),(13,13),(13,13),(9,11),(7,10),(7,7),(13,13),(4,4)) val startcoords = Array(null, Array((3,9,1),(2,3,0)), Array((0,0,0),(12,0,0)), Array((0,0,0),(12,0,0)), Array((5,4,1),(4,4,0)), Array((4,0,1),(6,2,1)), Array((4,3,0),(5,3,0)), Array((0,0,0),(12,0,0)), Array((0,0,0),(3,3,1)) ) val apples = Array(null, Array( // Level 1 Array((3,7,0),(3,5,0),(3,3,0),(3,2,1),(4,2,0),(5,2,1),(5,5,1),(5,8,0),(6,8,1),(7,8,0),(7,5,0),(7,2,1),(8,2,0),(9,2,1),(9,5,1),(9,8,0),(10,8,1),(11,8,0),(11,5,0),(11,2,1),(12,2,0),(13,2,1),(13,5,1),(13,8,0),(14,8,1),(15,8,0),(15,5,0),(15,2,1),(16,2,0),(17,2,1),(17,5,1),(17,7,1),(17,8,0),(18,8,1),(18,7,0),(19,7,1),(19,8,0),(18,9,0),(17,9,1)), Array((2,7,0),(2,8,0),(1,8,1),(1,9,1),(2,9,0),(4,9,0),(16,9,0),(16,7,0),(1,7,1),(16,6,0),(1,6,1),(16,5,0),(1,5,1),(16,4,0),(1,4,1),(16,3,0)) ), Array( // Level 2 Array((1,1,0),(2,2,0),(3,2,0),(7,2,1),(6,2,1),(6,2,0),(7,2,0),(7,10,1),(7,10,0),(5,10,1),(6,10,1),(6,10,0),(5,10,0),(5,2,1),(5,2,0),(8,2,1),(8,2,0),(8,10,1),(8,10,0),(4,10,1),(4,10,0),(4,2,1),(4,2,0),(10,1,0),(11,0,0),(11,0,1),(10,1,1),(9,2,1),(9,2,0),(9,10,1),(10,10,1),(11,11,1),(12,12,1),(12,12,0),(11,11,0),(10,10,0),(9,10,0),(2,11,0),(1,12,0),(1,12,1),(2,11,1),(3,10,1),(3,10,0),(3,2,1),(2,2,1),(1,1,1),(0,0,1)), Array((11,1,0),(10,2,0),(10,3,0),(10,7,1),(10,6,1),(10,6,0),(10,7,0),(2,7,1),(2,7,0),(2,5,1),(2,6,1),(2,6,0),(2,5,0),(10,5,1),(10,5,0),(10,8,1),(10,8,0),(2,8,1),(2,8,0),(2,4,1),(2,4,0),(10,4,1),(10,4,0),(11,10,0),(12,11,0),(12,11,1),(11,10,1),(10,9,1),(10,9,0),(2,9,1),(2,10,1),(1,11,1),(0,12,1),(0,12,0),(1,11,0),(2,10,0),(2,9,0),(1,2,0),(0,1,0),(0,1,1),(1,2,1),(2,3,1),(2,3,0),(10,3,1),(10,2,1),(11,1,1),(12,0,1)) ), Array( // Level 3 Array((1,1,0),(2,2,0),(3,2,0),(7,2,1),(6,2,1),(6,2,0),(7,2,0),(7,10,0),(5,10,0),(5,2,1),(5,2,0),(10,1,0),(11,0,0),(11,0,1),(10,1,1),(9,2,1),(9,2,0),(9,10,0),(9,10,1),(4,10,1),(4,10,0),(4,2,1),(4,2,0),(8,2,1),(8,2,0),(8,10,0),(8,10,1),(7,10,1),(5,10,1),(6,10,1),(6,10,0),(10,6,1),(10,6,0),(10,7,0),(10,8,0),(10,9,0),(10,10,0),(10,10,1),(11,11,1),(12,12,1),(12,12,0),(11,11,0),(2,11,0),(1,12,0),(1,12,1),(2,11,1),(3,10,1),(3,10,0),(3,2,1),(2,2,1),(1,1,1),(0,0,1)), Array((11,1,0),(10,2,0),(10,3,0),(10,7,1),(2,7,1),(2,7,0),(2,5,1),(2,6,1),(2,6,0),(2,5,0),(10,5,1),(10,5,0),(10,8,1),(2,8,1),(2,8,0),(2,4,1),(2,4,0),(10,4,1),(10,4,0),(11,10,0),(12,11,0),(12,11,1),(11,10,1),(10,9,1),(2,9,1),(2,10,1),(1,11,1),(0,12,1),(0,12,0),(1,11,0),(2,10,0),(2,9,0),(1,2,0),(0,1,0),(0,1,1),(1,2,1),(2,3,1),(2,3,0),(10,3,1),(10,2,1),(11,1,1),(12,0,1)) ), Array( // Level 4 Array((7,4,1),(7,3,0),(7,7,1),(7,6,0),(2,6,0),(2,5,1),(2,7,0),(3,8,1),(6,8,1),(7,8,0),(7,9,1),(7,10,0),(8,10,1),(8,9,0),(8,1,1),(8,0,0),(7,0,1),(7,1,0),(7,2,1),(6,2,0),(3,2,0),(2,2,1),(2,3,0),(2,4,1)), Array((4,9,1),(3,1,1),(3,9,0),(6,1,1),(6,9,1),(5,1,1),(5,9,0),(4,1,1)) ), Array( // Level 5 Array((6,0,1),(5,0,0),(5,2,0),(5,3,1),(5,4,0),(5,6,0),(5,7,1),(5,8,0),(3,8,0),(3,7,1),(2,8,1),(1,8,0),(1,6,0),(1,5,1),(1,4,0),(1,2,0),(1,1,1),(0,0,1),(1,0,0),(2,0,1),(3,0,0)), Array((6,1,0),(5,1,1),(4,1,0),(2,1,0),(0,1,0),(0,2,1),(0,3,0),(0,4,1),(1,3,1),(2,4,1),(2,3,0),(3,4,0),(3,6,0),(2,5,0),(0,5,0),(0,6,1),(0,7,0),(0,8,1),(1,7,1),(2,7,0),(2,9,0),(0,9,0),(1,9,1),(3,9,1),(4,9,0),(6,9,0),(5,9,1),(4,8,1),(4,7,0),(6,7,0),(6,8,1),(6,6,1),(6,5,0),(5,5,1),(3,5,1),(2,6,1),(4,6,1),(4,5,0),(4,4,1),(3,3,1),(3,1,1),(2,2,1),(3,2,0),(4,2,1),(4,3,0),(6,3,0),(6,4,1)) ), Array( // Level 6 Array((3,6,0),(3,6,1),(3,4,1),(3,4,0),(0,3,0),(0,3,1),(2,3,1),(2,3,0),(3,0,0),(3,0,1),(3,2,1),(3,2,0),(6,3,0),(6,3,1),(4,3,1)), Array((3,5,1),(3,5,0),(1,3,1),(1,3,0),(3,1,1),(3,1,0),(5,3,1)) ), Array( // Level 7 Array((1,1,0),(2,2,0),(3,2,0),(7,2,1),(6,2,1),(6,2,0),(7,2,0),(7,10,1),(7,10,0),(5,10,1),(6,10,1),(6,10,0),(5,10,0),(5,2,1),(5,2,0),(8,2,1),(8,2,0),(8,10,1),(8,10,0),(4,10,1),(4,10,0),(4,2,1),(4,2,0),(10,1,0),(11,0,0),(11,0,1),(10,1,1),(9,2,1),(9,2,0),(9,10,1),(10,10,1),(11,11,1),(12,12,1),(12,12,0),(11,11,0),(10,10,0),(9,10,0),(2,11,0),(1,12,0),(1,12,1),(2,11,1),(3,10,1),(3,10,0),(3,2,1),(2,2,1),(1,1,1),(0,0,1)), Array((11,1,0),(10,2,0),(10,3,0),(10,7,1),(10,6,1),(10,6,0),(10,7,0),(2,7,1),(2,7,0),(2,5,1),(2,6,1),(2,6,0),(2,5,0),(10,5,1),(10,5,0),(10,8,1),(10,8,0),(2,8,1),(2,8,0),(2,4,1),(2,4,0),(10,4,1),(10,4,0),(11,10,0),(12,11,0),(12,11,1),(11,10,1),(10,9,1),(10,9,0),(2,9,1),(2,10,1),(1,11,1),(0,12,1),(0,12,0),(1,11,0),(2,10,0),(2,9,0),(1,2,0),(0,1,0),(0,1,1),(1,2,1),(2,3,1),(2,3,0),(10,3,1),(10,2,1),(11,1,1),(12,0,1)) ), Array( // Level 8 Array((2,2,0),(1,2,1)), Array((0,3,1),(1,3,0),(1,1,0),(2,1,1),(3,0,0),(3,1,0)) ) ) } class SnakeGame(val level : Int) { implicit def asRunnable(f : () => Unit) = new Runnable() { def run() = f() } val startcoord = SnakeGame.startcoords(level) val coord = startcoord.clone() val dir = Array((1,0,0),(1,0,0)) val lastdir = Array((0,0,0),(0,0,0)) val (width, height) = SnakeGame.dimensions(level) val squares = Array.ofDim[Int](2,height,width) val food = SnakeGame.apples(level) val stage = Array(0,0) for((l,i) <- food.zipWithIndex) for (((x,y,z),j) <- l.zipWithIndex) { squares(z)(y)(x)= if(j==0) i+3 else 5 } val path = Array(new ArrayBuffer[(Int,Int,Int)], new ArrayBuffer[(Int,Int,Int)]) var skip = true for(i <- 0 until 2) { val (x,y,z)=coord(i) path(i).append((x,y,z)) squares(z)(y)(x)=i+1 } def advance() { if (skip) { skip = false return } for(i <- 0 until 2) { if(stage(i)<2) { val (x,y,z) = Snake.addTuple(coord(i),dir(i)) if(dir(i)._3!=0) { val tmp = lastdir(i) lastdir(i) = dir(i) dir(i) = tmp } else { lastdir(i) = dir(i) } if (x<0 || x>=width || y<0 || y>=height || squares(z)(y)(x)!=0 && squares(z)(y)(x)!=i+3) { if(stage(i)==1&&(x,y,z)==startcoord(i)) { stage(i)=2 if(stage(1-i)==2) SwingUtilities.invokeLater( () => Snake.levelComplete() ) } else { squares(coord(i)._3)(coord(i)._2)(coord(i)._1)=6 stage(i)=3 if(stage(1-i)<=1) stage(1-i)=4 } } else { if(squares(z)(y)(x)==i+3) { val n = food(i).indexOf((x,y,z))+1 if(n < food(i).length) { val (xf, yf, zf) = food(i)(n) squares(zf)(yf)(xf) = i+3 } else stage(i)=1 } squares(z)(y)(x)=i+1 coord(i)=(x,y,z) path(i).append((x,y,z)) } } } } } object Snake extends JFrame { val snakeColors = Array(new Color(255,255,255),new Color(255,0,0),new Color(0,255,0),new Color(255,128,128),new Color(128,255,128),new Color(192,192,192),new Color(64,64,64)) def addTuple(p : (Int,Int,Int), q : (Int,Int,Int)) = { (p._1+q._1,p._2+q._2,p._3+q._3) } object MenuListener extends ActionListener { override def actionPerformed(e : ActionEvent) { e.getActionCommand match { case "Controls" => JOptionPane.showMessageDialog(Snake,"Game Controls\n\nGeneral\nSpace: Pause/Unpause\nN: Advance 1 step\n\nRed Snake\nLeft: Go left\nRight: Go right\nUp: Go up\nDown: Go Down\nEnter: Go In/Out\n\nGreen Snake\nA: Go Left\nD: Go Right\nW: Go Up\nS: Go Down\nQ: Go In/Out") case "Restart current level" => { setLevel(game.level) } case "Quit" => { dispose(); timer.stop() } case _ => if(e.getActionCommand.startsWith("Level")) { val l = (e.getActionCommand.last-'0').asInstanceOf[Int] if(l!=game.level) setLevel(l) } else println(e.getActionCommand()) } } } object TimerListener extends ActionListener { override def actionPerformed(e : ActionEvent) { advance() } } object KeyboardListener extends KeyListener { def keyPressed(e : KeyEvent) { e.getKeyCode() match { case KeyEvent.VK_DOWN => if(game.lastdir(0)._2>=0) game.dir(0) = (0,1,0) case KeyEvent.VK_UP => if(game.lastdir(0)._2<=0) game.dir(0) = (0,-1,0) case KeyEvent.VK_LEFT => if(game.lastdir(0)._1<=0) game.dir(0) = (-1,0,0) case KeyEvent.VK_RIGHT => if(game.lastdir(0)._1>=0) game.dir(0) = (1,0,0) case KeyEvent.VK_ENTER => if(game.lastdir(0)._3==0) game.dir(0) = (0,0,1-2*game.coord(0)._3) case KeyEvent.VK_S => if(game.lastdir(1)._2>=0) game.dir(1) = (0,1,0) case KeyEvent.VK_W => if(game.lastdir(1)._2<=0) game.dir(1) = (0,-1,0) case KeyEvent.VK_A => if(game.lastdir(1)._1<=0) game.dir(1) = (-1,0,0) case KeyEvent.VK_D => if(game.lastdir(1)._1>=0) game.dir(1) = (1,0,0) case KeyEvent.VK_Q => if(game.lastdir(1)._3==0) game.dir(1) = (0,0,1-2*game.coord(1)._3) case KeyEvent.VK_SPACE => { if(timer.isRunning()) timer.stop() else timer.start() updateTitle() } case KeyEvent.VK_N => advance() case _ => () } repaint() } def keyReleased(e : KeyEvent) {} def keyTyped(e : KeyEvent) {} } def advance() { game.advance() repaint() updateTitle() } def updateTitle() { if(game.stage(0)>2 || game.stage(1)>2) setTitle("Snake - Game Over") else if(game.stage(0)==2 && game.stage(1)==2) setTitle("Snake - Level Complete") else if(timer.isRunning()) setTitle("Snake") else setTitle("Snake - Paused") } def setLevel(lev : Int) { game = new SnakeGame(lev) if(timer.isRunning()) { timer.stop() timer.start() } updateTitle() repaint() } def levelComplete() { JOptionPane.showMessageDialog(Snake,"Level complete.") } var game = new SnakeGame(1) setSize(800,600) setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) getContentPane().setLayout(new BoxLayout(getContentPane(),BoxLayout.X_AXIS)) add(new SnakePic()) val bar = new JMenuBar() setJMenuBar(bar) val gameMenu = new JMenu("Game") gameMenu.setMnemonic('G') val helpMenu = new JMenu("Help") helpMenu.setMnemonic('H') val restartItem = gameMenu.add("Restart current level") restartItem.setMnemonic('R') restartItem.setAccelerator(KeyStroke.getKeyStroke("control R")) gameMenu.addSeparator() val buttonGroup = new ButtonGroup() val levelItems = new Array[JMenuItem](9) for(i <- 1 to 8) { val item = new JRadioButtonMenuItem("Level %d".format(i)) item.setMnemonic('0'+i.asInstanceOf[Char]) if(i==1) item.setSelected(true) item.addActionListener(MenuListener) gameMenu.add(item) buttonGroup.add(item) levelItems(i)=item } gameMenu.addSeparator() gameMenu.add("Quit").setMnemonic('Q') helpMenu.add("Controls").setMnemonic('C') bar.add(gameMenu) bar.add(helpMenu) for(i <- 0 until bar.getMenuCount()) for(j <- 0 until bar.getMenu(i).getItemCount()) { val item = bar.getMenu(i).getItem(j) if(item!=null) item.addActionListener(MenuListener) } addKeyListener(KeyboardListener) val timer = new Timer(1000,TimerListener) timer.start() def main(s : Array[String]) { updateTitle() setVisible(true) } }