Created
May 23, 2013 07:26
-
-
Save yaroslav-ulanovych/5633278 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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