Hallo Habr! Wenn ich eine neue Sprache lerne, mache ich normalerweise eine Schlange darauf. Vielleicht interessiert sich ein Neuling, der auch Scala studiert, für den Code eines anderen Neulings in diesem PL. Für erfahrene Rocker ist mein erster Scala-Code wahrscheinlich traurig. Für Details willkommen bei Katze.Inhalt
- Scala lernen: Teil 1 - Das Schlangenspiel
Verweise
QuellcodeÜber das Spiel
Um mit Grafiken zu arbeiten, wurde libGdx im LWJGL-Backend verwendet. Die Verwaltung erfolgt über die Pfeile auf der Tastatur.Domain
Die Fallklasse wurde verwendet, um die Trockenheiten zu simulieren, da sie nicht veränderlich sind, nach Wert verglichen werden und im Allgemeinen der Aufzeichnung von Haskell ähnlich sind.Punkt im 2D-Raum:case class Point(x: Int, y: Int)
Fahrtrichtung. Unter Verwendung des übereinstimmenden Musters können diese Klassen als Aufzählung verwendet werden:
sealed abstract class Direction
case object Up extends Direction
case object Down extends Direction
case object Right extends Direction
case object Left extends Direction
Der Rahmen, in dem sich die Schlange bewegtcase class Frame(min: Point, max: Point) {
def points = {
for (i <- min.x until max.x + 1;
j <- min.y until max.y + 1
if i == min.x ||
i == max.x ||
j == min.y ||
j == max.y)
yield Point(i, j)
}
}
Futter für die Schlange:case class Food(body: Point, random: Random) {
def moveRandomIn(frame: Frame): Food = {
val x = random.between(frame.min.x + 1, frame.max.x)
val y = random.between(frame.min.y + 1, frame.max.y)
copy(body = Point(x, y))
}
}
Schlange:case class Snake(body: List[Point], direction: Direction) {
def move(): Snake = {
val point = direction match {
case Up() => body.head.copy(y = body.head.y + 1)
case Down() => body.head.copy(y = body.head.y - 1)
case Left() => body.head.copy(x = body.head.x - 1)
case Right() => body.head.copy(x = body.head.x + 1)
}
copy(body = point :: body.filter(p => p != body.last))
}
def turn(direction: Direction): Snake = {
copy(direction = direction)
}
def eat(food: Food): Snake = {
copy(body = food.body :: body)
}
def canEat(food: Food): Boolean = {
food.body == body.head
}
def headIsIn(frame: Frame): Boolean = {
body.head.x < frame.max.x &&
body.head.y < frame.max.y &&
body.head.x > frame.min.x &&
body.head.y > frame.min.y
}
def isBitTail() = {
body.tail.exists(p => p == body.head)
}
}
Ein Spiel:package domain
case class Game(food: Food, snake: Snake, frame: Frame, elapsedTime: Float, start: Snake) {
val framePoints = frame.points.toList
def handle(input: List[Direction]): Game = {
if (input.isEmpty) {
this
} else {
copy(snake = input.foldLeft(snake)((s, d) => s.turn(d)))
}
}
def update(deltaTime: Float): Game = {
val elapsed = elapsedTime + deltaTime
if (elapsed > 0.1) {
val game = copy(snake = snake.move(), elapsedTime = 0)
if (!game.snake.headIsIn(frame)) {
game.reset()
} else if (game.snake.isBitTail()) {
game.reset()
} else if (game.snake.canEat(food)) {
game.copy(snake = snake.eat(food), food = food.moveRandomIn(frame))
} else {
game
}
} else {
copy(elapsedTime = elapsed)
}
}
def reset() = copy(snake = start)
def points: List[Point] = {
(food.body :: snake.body) ::: framePoints
}
}
Präsentation
Eine Klasse, die Informationen zu gedrückten Tasten sammeltimport com.badlogic.gdx.{InputAdapter}
class InputCondensate extends InputAdapter {
private var keys: List[Int] = Nil
def list: List[Int] = keys.reverse
def clear(): Unit = {
keys = Nil
}
override def keyDown(keycode: Int): Boolean = {
keys = keycode :: keys
true
}
}
Die Klasse, die die Anzeige des Spiels steuert:import com.badlogic.gdx.Input.Keys
import com.badlogic.gdx.graphics.glutils.ShapeRenderer
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType
import com.badlogic.gdx.graphics.{Color, GL20}
import com.badlogic.gdx.{Game, Gdx}
class SnakeGame(var game: domain.Game, val sizeMultiplayer: Float) extends Game {
lazy val prs = new InputCondensate
lazy val shapeRenderer: ShapeRenderer = new ShapeRenderer()
override def create(): Unit = {
Gdx.input.setInputProcessor(prs)
}
override def render(): Unit = {
game = game
.handle(prs.list.map(i => i match {
case Keys.UP => domain.Up()
case Keys.DOWN => domain.Down()
case Keys.LEFT => domain.Left()
case Keys.RIGHT => domain.Right()
}))
.update(Gdx.graphics.getDeltaTime())
prs.clear()
Gdx.gl.glClearColor(1, 1, 1, 1)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
shapeRenderer.setColor(Color.BLACK)
shapeRenderer.begin(ShapeType.Filled)
for (p <- game.points)
shapeRenderer.circle(p.x * sizeMultiplayer, p.y * sizeMultiplayer, sizeMultiplayer / 2)
shapeRenderer.end()
}
}
Der Haupteinstiegspunkt des Spiels:import com.badlogic.gdx.backends.lwjgl.{LwjglApplication, LwjglApplicationConfiguration}
import scala.util.Random
object Main extends App {
val config = new LwjglApplicationConfiguration
config.title = "Scala Snake Game"
config.width = 300
config.height = 300
val food = domain.Food(domain.Point(4, 4), new Random())
val frame = domain.Frame(domain.Point(0, 0), domain.Point(30, 30))
val snake = domain.Snake(domain.Point(5, 5) :: domain.Point(6, 6) :: domain.Point(7, 7) :: Nil, domain.Right())
val game = domain.Game(food, snake, frame, 0, snake)
new LwjglApplication(new SnakeGame(game, 10), config)
}