Presentation 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.

Speakers


PDF: slides.pdf

Slides

Fun Programming in Scala:

Fun Programming in Scala: Games, Algorithms, and Apps Shadaj Laddad youtube.com/shadajProgramming github.com/shadaj @ShadajL

About Me

About Me • A middle schooler • A programmer • Logo, NXT, Ruby, Python, C, and Scala! • Loves Math and Science

Algorithms

Algorithms

Sudoku Solving

Sudoku Solving

Sudoku Solving

Sudoku Solving github.com/shadaj/sudoku

Solving Sudoku (Simple)

Solving Sudoku (Simple)

Solving Sudoku (Simple)

Solving Sudoku (Simple) ...

Solving Sudoku (Simple)

Solving Sudoku (Simple) ...

Solving Sudoku (Simple)

Solving Sudoku (Simple) Recursion ...

Solving Sudoku (Smarter)

Solving Sudoku (Smarter)

Solving Sudoku (Smarter)

Solving Sudoku (Smarter) ...

Solving Sudoku (Smarter)

Solving Sudoku (Smarter) Recursion ...

Cells

Cells object Cells { type Cell = Option[Int] implicit def intToCell(num: Int): Cell = Some(num) }

The Grid

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

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

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

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

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

GCJ and Rosalind

MinScalarProduct Input

MinScalarProduct Input 2 3 1 3 -5 -2 4 1 5 1 2 3 4 5 1 0 1 0 1

MinScalarProduct Input

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

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

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

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

Output Case #1: -25 Case #2: 6

Solving

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

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

Parsing

Parsing github.com/shadaj/gcj-parser

Conversions

Conversions trait Converter[T] { def convert(string: String): T }

Example Converters

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

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

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

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

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

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

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

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

Apps

+

+ + + g8 shadaj/scala-android

WordSteal

WordSteal github.com/ shadaj/wordsteal

WordSteal

WordSteal github.com/ shadaj/wordsteal

The Activity

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

Games

Minecraft Mods

Minecraft Mods

Minecraft Mods

Minecraft Mods Server

Minecraft Mods

Minecraft Mods Server Client

Minecraft Mods

Minecraft Mods Server via Sockets Client

Minecraft Mods

Minecraft Mods Server via Sockets Client Your Mods

Minecraft Mods

Minecraft Mods Server via Sockets Client Your Mods g8 shadaj/scala-minecraft

Fill Area Mod

Fill Area Mod

The Code

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

Scala in School Spirit Day Templates

TechTalks

TechTalks

NeuroScala

NeuroScala

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

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

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

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

Demo - MindSnakey

Demo - MindSnakey github.com/shadaj/mind-snakey

The Setup

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

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

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)

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

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

What’s Next

What’s Next

What’s Next

Follow me online!

Follow me online! • youtube.com/shadajProgramming • github.com/shadaj • twitter.com/ShadajL

A

A L ! A S C E S L U R