Want to dive even deeper?

Take the course Building Games with Scratch 2.0 by Dennis G. Jerz and become an expert!
Building Games with Scratch 2.0
by Dennis G. Jerz

Check it out!
You're watching a preview of this video, click the button on the left to puchase the full version from Devoxx'08.

Fun Programming in Scala: Games, Algorithms, and Apps

Scala continues to be fun! Implementing algorithms to solve Sudoku puzzles, bioinformatic problems, and analyzing time-series data has been a great learning experience. Creating video games and mods have made my game time much more enjoyable and gave me an opportunity to impress my friends! To do all these, I used a myriad of technologies from Scala IDE, sbt, and giter8 to git, Android, and Play! In the process, I learned a lot more about Scala's features such as traits, iterators, streams, lazy vals, implicit classes, and typeclasses. In this talk, I will share a few games, Android apps, and algorithms that show how Scala made implementing complex programs simple. I will also demonstrate a few awesome Minecraft mods written in Scala and a "mind blowing" project.

Published on
  • 3.501
  • 18
  • 2
  • 10
  • 0
  • Fun Programming in Scala: Games, Algorithms, and Apps Shadaj Laddad youtube.com/shadajProgramming github.com/shadaj @ShadajL
  • About Me • A middle schooler • A programmer • Logo, NXT, Ruby, Python, C, and Scala! • Loves Math and Science
  • Algorithms
  • Sudoku Solving
  • Sudoku Solving github.com/shadaj/sudoku
  • Solving Sudoku (Simple)
  • Solving Sudoku (Simple) ...
  • Solving Sudoku (Simple) ...
  • Solving Sudoku (Simple) Recursion ...
  • Solving Sudoku (Smarter)
  • Solving Sudoku (Smarter) ...
  • Solving Sudoku (Smarter) Recursion ...
  • Cells object Cells { type Cell = Option[Int] implicit def intToCell(num: Int): Cell = Some(num) }
  • The Grid class val val val Grid(data: IndexedSeq[IndexedSeq[Cell]], boxWidth: Int, boxHeight: Int) { size = boxWidth*boxHeight boxesInWidth = size/boxWidth boxesInHeight = size/boxHeight def updated(x: Int, y: Int, value: Cell) = { new Grid(data.updated(y, data(y).updated(x, value)), boxWidth, boxHeight) } def apply(x: Int, y: Int) = ... def column(x: Int): IndexedSeq[Cell] = ... def row(y: Int): IndexedSeq[Cell] = ... def blockFor(x: Int, y: Int): IndexedSeq[Cell] = ... def possibleValues(x: Int, y: Int): IndexedSeq[Int] = ...
  • Solving the Puzzle def solve(data: Grid): Option[Grid] = { val filledIn = fill(data) if (filledIn.solved) { Some(filledIn) } else { guess(filledIn).view.map(solve).collectFirst{case Some(grid) => grid} } }
  • Filling in Known Values @tailrec def fill(data: Grid): Grid = { val possibleCoordinates = for (x <- 0 until data.size; y <- 0 until data.size) yield (x, y) val coordinatesToFill = possibleCoordinates.view.map { case (x, y) => (x, y, data.possibleValues(x, y)) }.collectFirst { case (x, y, possibleValues) if (possibleValues.length == 1) => (x, y, possibleValues.head) } coordinatesToFill match { case Some((x, y, value)) => { fill(data.updated(x, y, value)) } case None => data } }
  • Guessing def guess(data: Grid): Seq[Grid] = { val allCoordinates = for (x <- 0 until data.size; y <- 0 until data.size) yield (x, y) val Some((x,y)) = allCoordinates.collectFirst { case c@(x, y) if (!data(x, y).isDefined) => c } val possibilities = data.possibleValues(x, y) possibilities.map { p => data.updated(x, y, p) } }
  • Lessons Learned • Avoid mutable state, it will bite you • There is always a better way • Great improvements between the simple and smarter algorithms • Lazy can be good :D
  • GCJ and Rosalind
  • MinScalarProduct Input 2 3 1 3 -5 -2 4 1 5 1 2 3 4 5 1 0 1 0 1
  • MinScalarProduct Input 2 3 1 3 -5 -2 4 1 5 1 2 3 4 5 1 0 1 0 1 Number of test cases
  • MinScalarProduct Input 2 3 1 3 -5 -2 4 1 5 1 2 3 4 5 1 0 1 0 1 Number of test cases Items in each vector
  • MinScalarProduct Input 2 3 1 3 -5 -2 4 1 5 1 2 3 4 5 1 0 1 0 1 Number of test cases Items in each vector Vector 1 Vector 2 Test Case 1
  • MinScalarProduct Input 2 3 1 3 -5 -2 4 1 5 1 2 3 4 5 1 0 1 0 1 Number of test cases Items in each vector Vector 1 Vector 2 Test Case 1 Test Case 2
  • Output Case #1: -25 Case #2: 6
  • Solving class MinScalarTestCase(val testCaseNumber: Int, n: Int, v1: Seq[Long], v2: Seq[Long]) extends TestCase { def solve = { Solution(v1.sorted.zip(v2.sorted.reverse).map(t => t._1 * t._2).sum.toString, testCaseNumber) } } object MinScalarLauncher extends ProblemLauncher[MinScalarTestCase]("A", 1) { val converter = new TestCaseSeqConverter[MinScalarTestCase] { override def parseTestCase(lines: Iterator[String], testCaseNumber: Int) = { new MinScalarTestCase(testCaseNumber, lines.next.convert[Int], lines.next.convertSeq[Long](), lines.next.convertSeq[Long]()) } } }
  • Solving class MinScalarTestCase(val testCaseNumber: Int, n: Int, v1: Seq[Long], v2: Seq[Long]) extends TestCase { def solve = { Solution(v1.sorted.zip(v2.sorted.reverse).map(t => t._1 * t._2).sum.toString, testCaseNumber) } } object MinScalarLauncher extends ProblemLauncher[MinScalarTestCase]("A", 1) { val converter = new TestCaseSeqConverter[MinScalarTestCase] { override def parseTestCase(lines: Iterator[String], testCaseNumber: Int) = { new MinScalarTestCase(testCaseNumber, lines.next.convert[Int], lines.next.convertSeq[Long](), lines.next.convertSeq[Long]()) } } }
  • Parsing
  • Parsing github.com/shadaj/gcj-parser
  • Conversions trait Converter[T] { def convert(string: String): T }
  • Example Converters package object parser { implicit object IntConverter extends Converter[Int] { def convert(string: String) = string.toInt } ... implicit object BigIntConverter extends Converter[BigInt] { def convert(string: String) = math.BigInt(string) } ... }
  • Converting from a String implicit class ConvertingString(val string: String) extends AnyVal { def convert[T](implicit converter: Converter[T]): T = { converter.convert(string) } def convertSeq[T](tokenizer: Converter[Seq[String]] = SpaceRepeatingConverter) (implicit converter: Converter[T]): Seq[T] = { string.convert(tokenizer).map(_.convert(converter)) } }
  • Converting from a String implicit class ConvertingString(val string: String) extends AnyVal { def convert[T](implicit converter: Converter[T]): T = { converter.convert(string) } def convertSeq[T](tokenizer: Converter[Seq[String]] = SpaceRepeatingConverter) (implicit converter: Converter[T]): Seq[T] = { string.convert(tokenizer).map(_.convert(converter)) } } "1".convert[Int] "1 2 3".convertSeq[Int]() """1 2 3""".convertSeq[Int](LineRepeatingConverter)
  • Converting Test Cases trait TestCaseSeqConverter[T <: TestCase] extends Converter[Seq[T]] { def parseTestCase(lines: Iterator[String], testCaseNumber: Int): T def convert(string: String) = { val lines = Source.fromString(string).getLines lines.foldLeft((1, Seq[T]())) {case ((n, testCases), cur) => (n + 1, testCases :+ parseTestCase(Iterator.single(cur) ++ lines, n)) }._2 } }
  • Problems class Problem[T <: TestCase: TestCaseSeqConverter] { def solve(problemName: String, unneededLines: Int) { val input = Source.fromFile(problemName + ".in") val output = new PrintWriter(problemName + ".out") input.getLines.drop(unneededLines) val testCases: Seq[T] = input.parse val solutions = solve(testCases).sortBy(_.problemNumber) solutions.foreach { p => output.println("Case #" + p.problemNumber + ": " + p.answer) } output.close } def solve(testCases: Seq[T]) = { timed("Total Time: ") { testCases.map(t => timed(s"Test Case #${t.testCaseNumber} took ") { t.solve }) } } }
  • Problems class Problem[T <: TestCase: TestCaseSeqConverter] { def solve(problemName: String, unneededLines: Int) { val input = Source.fromFile(problemName + ".in") val output = new PrintWriter(problemName + ".out") input.getLines.drop(unneededLines) val testCases: Seq[T] = input.parse val solutions = solve(testCases).sortBy(_.problemNumber) solutions.foreach { p => output.println("Case #" + p.problemNumber + ": " + p.answer) } output.close } def solve(testCases: Seq[T]) = { timed("Total Time: ") { testCases.map(t => timed(s"Test Case #${t.testCaseNumber} took ") { t.solve }) } } } A test case such that there is a converter
  • Problems A test case such that there is a converter class Problem[T <: TestCase: TestCaseSeqConverter] { def solve(problemName: String, unneededLines: Int) { val input = Source.fromFile(problemName + ".in") val output = new PrintWriter(problemName + ".out") input.getLines.drop(unneededLines) which allows... def parse[T <: TestCase] (implicit converter: TestCaseSeqConverter[T]) val testCases: Seq[T] = input.parse val solutions = solve(testCases).sortBy(_.problemNumber) solutions.foreach { p => output.println("Case #" + p.problemNumber + ": " + p.answer) } output.close } def solve(testCases: Seq[T]) = { timed("Total Time: ") { testCases.map(t => timed(s"Test Case #${t.testCaseNumber} took ") { t.solve }) } } }
  • ProblemLauncher abstract class ProblemLauncher[T <: TestCase](problemName: String, unneededLines: Int = 1) { val problem: Problem[T] = new Problem[T]()(converter) val converter: TestCaseSeqConverter[T] def main(args: Array[String]) = { problem.solve(problemName, unneededLines) } }
  • Apps
  • + + + g8 shadaj/scala-android
  • WordSteal github.com/ shadaj/wordsteal
  • WordSteal github.com/ shadaj/wordsteal
  • The Activity class WordStealActivity extends TypedActivity { lazy val charactersDisplay = findView(TR.characterDisplay) lazy val input = findView(TR.input) lazy val response = findView(TR.response) lazy val checkButton = findView(TR.checkButton) lazy val assets = getAssets() lazy val en_US = Source.fromInputStream(assets.open("en_US.dic")) lazy val words = en_US.getLines.map(_.toLowerCase).toSet override def onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) setContentView(R.layout.main) newLetters ... } ... }
  • Games
  • Minecraft Mods
  • Minecraft Mods Server
  • Minecraft Mods Server Client
  • Minecraft Mods Server via Sockets Client
  • Minecraft Mods Server via Sockets Client Your Mods
  • Minecraft Mods Server via Sockets Client Your Mods g8 shadaj/scala-minecraft
  • Fill Area Mod
  • The Code val s = start.get val e = end.get for ( x <- (s.getBlockX y <- (s.getBlockY z <- (s.getBlockZ ) { val blockToEdit = min e.getBlockX) to (s.getBlockX max e.getBlockX); min e.getBlockY) to (s.getBlockY max e.getBlockY); min e.getBlockZ) to (s.getBlockZ max e.getBlockZ) world.getBlockAt(x, y, z) blockToEdit.setTypeId(id) }
  • Scala in School Spirit Day Templates
  • TechTalks
  • NeuroScala
  • NeuroScala github.com/shadaj/neuro-thinkgear
  • How it Works
  • How it Works
  • How it Works
  • How it Works
  • How it Works
  • How it Works
  • How it Works Your Programs
  • The NeuroData types class NeuroData case class EEGPower(delta: Int, theta: Int, lowAlpha: Int, highAlpha: Int, lowBeta: Int, highBeta: Int, lowGamma: Int, highGamma: Int) extends NeuroData case class ESense(attention: Int, meditation: Int) extends NeuroData case class PoorSignalLevel(level: Int) extends NeuroData case class EEG(sense: ESense, power: EEGPower, poorSignalLevel: PoorSignalLevel) extends NeuroData case class Blink(power: Int) extends NeuroData case class RawData(level: Int) extends NeuroData
  • The Core - NeuroIterator class NeuroIterator(rawOutput: Boolean = false, jsonFormat: Boolean = true, host: String = "127.0.0.1", port: Int = 13854) extends Iterator[NeuroData] { val neuroSocket = new Socket(host, port) val neuroInput = new BufferedReader(new InputStreamReader(neuroSocket.getInputStream)) val neuroOutput = new OutputStreamWriter(neuroSocket.getOutputStream) def configure(rawOutput: Boolean = false, jsonFormat: Boolean = true) ... configure(rawOutput, jsonFormat) def hasNext = true def next = { neuroInput.readLine } } Implicit conversions!
  • Demo - MindSnakey
  • Demo - MindSnakey github.com/shadaj/mind-snakey
  • The Setup NeuroIterator NeuroData SnakeyActor Tick Timer D ,D raw wn ra ScreenActor Dra w, Mo Draw ve, n, T ic Tur k, n, ... SnakeActor
  • Sending NeuroData class NeuroSender extends Thread { ... val snakeyActor = host.system.actorFor("/user/snakeyActor") private lazy val in: NeuroIterator = { Try(new NeuroIterator) match { case Success(v) => { ... v } case Failure(e) => { ... in } } } override def run { while (!waitingForStop) { snakeyActor ! in.next } in.neuroSocket.close() } }
  • Sending Ticks and Draws object FrameRateUpdate extends ActionListener { def actionPerformed(event: ActionEvent) { snakeyActor ! Tick screenActor ! Draw } } val timer = new Timer(FRAME_RATE, FrameRateUpdate) timer.start()
  • The FSM (Finite State Machine) def running : Receive = { ... case StopGame => { ... context.become(stopped orElse common) } } def stopped : Receive = { case StartGame => { ... context.become(running orElse common) } } def common : Receive = ... def receive = stopped orElse common
  • Detecting which direction to turn case Tick => { snakeActor ! CheckEating(fruit, self) if (!waitingForBlink) { snakeActor ! Move } if (timeSinceBlink > DOUBLEBLINK && lastBlink != 0) { snakeActor ! TurnRight lastBlink = 0 } snakeActor ! Tick snakeActor ! CheckBadBlocks(badBlocks, self) } case Blink(power: Int) if power >= MIN_BLINK_POWER => { if (timeSinceBlink <= DOUBLEBLINK) { snakeActor ! TurnLeft lastBlink = 0 } else { lastBlink = System.currentTimeMillis } }
  • What’s Next
  • What’s Next
  • What’s Next
  • Follow me online! • youtube.com/shadajProgramming • github.com/shadaj • twitter.com/ShadajL
  • A L ! A S C E S L U R

Comments

Nicholas Sterling 2 years ago
What an amazing 13-year-old! In 15 years he'll be writing Scala's successor...
The kid is just brilliant!!

Login to add comments!