Skip to content

Instantly share code, notes, and snippets.

@yaroslav-ulanovych
Created May 23, 2013 07:26
Show Gist options
  • Save yaroslav-ulanovych/5633278 to your computer and use it in GitHub Desktop.
Save yaroslav-ulanovych/5633278 to your computer and use it in GitHub Desktop.
package com.mahpella.snake.core
import collection.mutable
import scala.collection.mutable.{ListBuffer, ArrayBuffer}
import scala.util.control.Exception._
class Field(map: GameMap) extends Object with ClockDriven {
val width = map.width
val height = map.height
case class X(value: Int) {
assert(value >= 0 && value < width.value)
}
object X {
val all = (0 to (width.value - 1)).map(X.apply)
}
case class Y(value: Int) {
assert(value >= 0 && value < height.value)
}
object Y {
val all = (0 to (height.value - 1)).map(Y.apply)
}
case class Point private(x: X, y: Y) {
def left(i: Int): Point = Point(X(x.value - i), y)
/** Returns a point next to this in given direction. */
def neighbour(direction: Direction, distance: Int = 1): Option[Point] = {
val (x, y) = (this.x.value, this.y.value)
Point(direction match {
case Direction.Left => (x - distance, y)
case Direction.Right => (x + distance, y)
case Direction.Up => (x, y - distance)
case Direction.Down => (x, y + distance)
})
}
}
object Point {
def random(): Point = {
val x = X(scala.util.Random.nextInt(width.value))
val y = Y(scala.util.Random.nextInt(height.value))
new Point(x, y)
}
/** Creates a point from integers, two arguments version. */
def apply(x: Int, y: Int): Option[Point] = apply((x, y))
/** Creates a point from integers, tuple argument version. */
def apply(t: (Int, Int)): Option[Point] = {
catching(classOf[AssertionError]) opt {
new Point(X(t._1), Y(t._2))
}
}
}
/** Returns all cells located at given point. */
def apply(point: Point): Seq[Cell] = {
columns(point.x.value)(point.y.value).toSeq
}
private val nextCellId = new SimpleIntBasedIdGenerator(identity)
abstract sealed class Cell(private var point: Point) {
val id = nextCellId()
_cellsAt(point) append this
_created append this
def x = point.x
def y = point.y
def position(): Point = point
def move(point: Point) {
val cells = _cellsAt(this.point)
cells.remove(cells.indexOf(this))
this.point = point
_cellsAt(point) append this
_moved append this
}
}
class Wall(point: Point) extends Cell(point) {
}
class Food(point: Point, value: Int) extends Cell(point){
}
/** All cells on the field. */
val cells = new Traversable[Cell] {
def foreach[U](fn: (Cell) => U) {
columns.foreach(_.foreach(_.foreach(fn)))
}
}
private def _cellsAt(point: Point) = columns(point.x.value)(point.y.value)
def getFreeCell(): Point = {
var result: Point = null
while(result == null) {
val point = Point.random()
if (apply(point).isEmpty) {
result = point
}
}
return result
}
def findFreeSpace(length: Int): (Point, Direction) = {
Array.fill(10)(
(Point.random(), Direction.random())
).find({case (point, direction) => {
(0 to (length - 1)).forall(i =>
point.neighbour(direction.opposite(), i).map(
x => _cellsAt(x).isEmpty
).getOrElse(false)
)
}}).get
}
private val columns = Array.fill(width.value) {
Array.fill(height.value) {
new ListBuffer[Cell]
}
}
private val _created = new ArrayBuffer[Cell]()
private val _moved = new ArrayBuffer[Cell]()
def created = _created.toList
def moved = _moved.toList
private val _snakes = new ArrayBuffer[Snake]()
override def tick() {
_created.clear()
_moved.clear()
_snakes.foreach(_.tick())
}
private val nextSnakeId = new SimpleIntBasedIdGenerator(identity)
class Snake(head: Point, private var direction: Direction) extends Object with ClockDriven {
val id = nextSnakeId()
_snakes append this
class SnakeElement(point: Point) extends Cell(point) {
val snake = Snake.this
}
private val turns = new Queue[Direction](8)
private val body = ArrayBuffer[SnakeElement]()
(0 to 2).map(i => {
val point = head.neighbour(direction.opposite(), i).get
body append new SnakeElement(point)
})
/** Move one cell further. */
def step() {
direction = turns.getOr(direction)
body(0).position().neighbour(direction) match {
case Some(point) => move(point)
case None =>
}
}
/**
* Doesn't do immediate turn, but puts in a queue of turns.
* Checks that it is really a turn.
*/
def turn(direction: Direction) {
val last = turns lastOr this.direction
if (last isTurn direction) {
turns put direction
}
}
def position(): Seq[Point] = {
body.map(_.position())
}
def move(point: Point) {
(point +: position().take(body.length - 1)).zip(body).foreach(x => x._2.move(x._1))
}
private var ticksToMove: Int = 2
override def tick() {
if (ticksToMove == 0) {
step()
ticksToMove = 2
} else {
ticksToMove -= 1
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment