سكالا التعلم: الجزء 1 - لعبة الأفعى


مرحباً هبر! عندما أتعلم لغة جديدة ، عادة ما أصنع ثعبانًا عليها. ربما يكون بعض المبتدئين الذين يدرسون سكالا مهتمين بشفرة مبتدئ آخر في هذا PL. بالنسبة إلى الروك المتمرسين ، من المرجح أن يكون رمز سكالا الأول حزينًا. لمزيد من التفاصيل ، مرحبا بك في القط.

المحتوى


  • سكالا التعلم: الجزء 1 - لعبة الأفعى

المراجع


مصدر الرمز

عن اللعبة


للعمل مع الرسومات ، تم استخدام libGdx في الواجهة الخلفية لـ LWJGL. تتم الإدارة باستخدام الأسهم الموجودة على لوحة المفاتيح.

نطاق


تم استخدام فئة الحالة لمحاكاة الجفاف لأنها غير قابلة للتغيير ، ويتم مقارنتها في القيمة وهي بشكل عام مماثلة للسجل من Haskell.

النقطة في مساحة ثنائية الأبعاد:

case class Point(x: Int, y: Int)

إتجاه السفر. باستخدام نمط المطابقة ، يمكن استخدام هذه الفئات كعدد:


    //         
    //enum Direction
    //{
    //    Up,
    //    Down,
    //    Right,
    //    Left
    //}
sealed abstract class Direction

case object Up extends Direction

case object Down extends Direction

case object Right extends Direction

case object Left extends Direction

الإطار الذي تتحرك فيه الأفعى

case 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)
  }
}

طعام الثعبان:

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))
  }
}

ثعبان:

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)
  }
}

لعبة:

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
  }
}

عرض


فصل يجمع معلومات حول الأزرار المضغوطة

import 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
  }
}

الفئة التي تتحكم في عرض اللعبة:

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()
  }
}

نقطة الدخول الرئيسية للعبة:

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)
}

All Articles