From 07df031670c33f8f4ae2eb8eb4f22ec2907a98a2 Mon Sep 17 00:00:00 2001
From: dalyjame <dalyjame@msu.edu>
Date: Sat, 6 Jul 2019 22:12:47 -0400
Subject: [PATCH] Now capable of saving and loading stages

---
 Builder/src/fmon/builder/MapBuilder.fxml      |  35 +++-
 Builder/src/fmon/builder/MapBuilder.scala     | 149 ++++++++++++++----
 FakeMon/src/fmon/Config.scala                 |   4 +-
 FakeMon/src/fmon/config.yaml                  |   1 +
 FakeMon/src/fmon/draw/Palette.scala           |   9 +-
 .../src/fmon/draw/tile/AutoFloorTile.scala    |   9 +-
 FakeMon/src/fmon/world/GameMap.scala          |  41 +++++
 FakeMon/src/fmon/world/Tileset.scala          |  18 +++
 8 files changed, 220 insertions(+), 46 deletions(-)
 create mode 100644 FakeMon/src/fmon/world/GameMap.scala
 create mode 100644 FakeMon/src/fmon/world/Tileset.scala

diff --git a/Builder/src/fmon/builder/MapBuilder.fxml b/Builder/src/fmon/builder/MapBuilder.fxml
index b2a1cdb..2149daf 100644
--- a/Builder/src/fmon/builder/MapBuilder.fxml
+++ b/Builder/src/fmon/builder/MapBuilder.fxml
@@ -8,6 +8,8 @@
 <?import javafx.scene.control.ScrollPane?>
 <?import javafx.scene.control.SeparatorMenuItem?>
 <?import javafx.scene.control.SplitPane?>
+<?import javafx.scene.control.Tab?>
+<?import javafx.scene.control.TabPane?>
 <?import javafx.scene.layout.AnchorPane?>
 <?import javafx.scene.layout.HBox?>
 <?import javafx.scene.layout.Pane?>
@@ -23,12 +25,12 @@
         <Menu mnemonicParsing="false" text="File">
           <items>
             <MenuItem mnemonicParsing="false" text="New" />
-            <MenuItem mnemonicParsing="false" text="Open…" />
+            <MenuItem mnemonicParsing="false" onAction="#open" text="Open…" />
             <Menu mnemonicParsing="false" text="Open Recent" />
             <SeparatorMenuItem mnemonicParsing="false" />
             <MenuItem mnemonicParsing="false" text="Close" />
-            <MenuItem mnemonicParsing="false" text="Save" />
-            <MenuItem mnemonicParsing="false" text="Save As…" />
+            <MenuItem mnemonicParsing="false" onAction="#save" text="Save" />
+            <MenuItem mnemonicParsing="false" onAction="#saveAs" text="Save As…" />
             <MenuItem mnemonicParsing="false" text="Revert" />
             <SeparatorMenuItem mnemonicParsing="false" />
             <MenuItem mnemonicParsing="false" text="Preferences…" />
@@ -59,11 +61,28 @@
     </MenuBar>
     <SplitPane dividerPositions="0.2505567928730512, 0.7505567928730512" focusTraversable="true" prefHeight="-1.0" prefWidth="-1.0" VBox.vgrow="ALWAYS">
       <items>
-        <AnchorPane>
-          <children>
-                  <TilePane fx:id="tileSelector" hgap="2.0" prefHeight="542.0" prefWidth="222.0" vgap="2.0" />
-          </children>
-        </AnchorPane>
+            <TabPane>
+               <tabs>
+                  <Tab closable="false" text="A">
+                     <content>
+                    <AnchorPane>
+                      <children>
+                              <TilePane fx:id="tileSelector" hgap="2.0" prefColumns="8" vgap="2.0" />
+                      </children>
+                    </AnchorPane>
+                     </content>
+                  </Tab>
+                  <Tab closable="false" text="B">
+                     <content>
+                        <AnchorPane>
+                           <children>
+                              <TilePane fx:id="doodadSelector" hgap="2.0" prefColumns="8" vgap="2.0" />
+                           </children>
+                        </AnchorPane>
+                     </content>
+                  </Tab>
+               </tabs>
+            </TabPane>
         <ScrollPane prefHeight="-1.0" prefWidth="-1.0">
           <content>
             <AnchorPane id="Content" minHeight="-1.0" minWidth="-1.0" prefHeight="545.0" prefWidth="430.0">
diff --git a/Builder/src/fmon/builder/MapBuilder.scala b/Builder/src/fmon/builder/MapBuilder.scala
index 53bc647..f8607a3 100644
--- a/Builder/src/fmon/builder/MapBuilder.scala
+++ b/Builder/src/fmon/builder/MapBuilder.scala
@@ -1,6 +1,9 @@
 package fmon.builder
 
 import java.io._
+import java.util.prefs.Preferences
+
+import scala.util.Properties
 
 import javafx.application.Application
 import javafx.fxml.FXML
@@ -17,9 +20,14 @@ import scalafx.scene.image.ImageView
 import scalafx.scene.input.MouseEvent
 import scalafx.scene.paint.Color
 import scalafx.scene.layout._
+import scalafx.stage.FileChooser
+import FileChooser.ExtensionFilter
 
+import fmon._
+import fmon.draw.Palette
 import fmon.draw.tile._
 import fmon.util.Direction
+import fmon.world._
 
 import Direction._
 
@@ -27,24 +35,41 @@ import scala.math.{min, max}
 
 class MapBuilder {
   @FXML var tileSelector: jfxsl.TilePane = _
+  @FXML var doodadSelector: jfxsl.TilePane = _
   @FXML var gameMap: jfxsl.TilePane = _
   
-  var tileSeq: IndexedSeq[AutoFloorTile] = _
   var imgSeq: IndexedSeq[ImageView] = _
   
-  var currTile: AutoFloorTile = _
+  var tileset: Tileset = _
+  var currTile: AutoTile = _
   
-  val numRows = 10
-  val numCols = 10
+  var level: GameMap = _
+  
+  var lastFile: File = _
+  
+  def lastDir(): String = {
+    val prefs = Preferences.userNodeForPackage(classOf[MapBuilder])
+    prefs.get("dir", Properties.userDir)
+  }
+  
+  def rememberDir(file: File): Unit = {
+    val dir = if (file.isDirectory()) file.getPath else file.getParent
+    if (dir != lastDir) {
+      val prefs = Preferences.userNodeForPackage(classOf[MapBuilder])
+      prefs.put("dir", dir)
+    }
+  }
   
   @FXML
   def initialize(): Unit = {
-    val file = new File(raw"C:\Users\dalyj\Documents\Design\Images\AutoTiles\tilea2.png")
-    val palette = new AutoTilePalette(file, 32)
-    val tiles = for (y <- 0 until 4; x <- 0 until 8) yield {
-      palette(x, y)
-    }
-    val icons = tiles.zipWithIndex.map{case(t, i) => {
+    tileset = new Tileset(new TilesetToken(raw"C:\Users\dalyj\Documents\Design\Images\AutoTiles\tilea2.png"))
+    level = new GameMap(10, 10, tileset)
+    setup()
+  }
+  
+  def setup(): Unit = {
+    
+    val icons = tileset.groundTiles.map(t => {
       val view = new ImageView(t.icon.croppedImage())
       view.handleEvent(MouseEvent.Any) {
         e: MouseEvent => if (e.eventType == MouseEvent.MouseClicked) {
@@ -52,33 +77,45 @@ class MapBuilder {
         }
       }
       view.delegate
-    }}
+    })
+    tileSelector.children.clear()
     tileSelector.children ++= icons
-    currTile = tiles.head
+    currTile = tileset.groundTiles.head
     
-    tileSeq = for (y <- 0 until numRows; x <- 0 until numCols) yield currTile
-    imgSeq = for (y <- 0 until numRows; x <- 0 until numCols) yield {
+    val doodadFile = new File(raw"C:\Users\dalyj\Documents\Design\Images\Icons\equipment-icons-tileset.png")
+    val doodadPalette = Palette.bySize(doodadFile, Config.tileSize, Config.tileSize)
+    
+    val doodads = doodadPalette.images.map(d => {
+      val view = new ImageView(d.croppedImage())
+      view.delegate
+    })
+    doodadSelector.children.clear()
+    doodadSelector.children ++= doodads 
+    
+    imgSeq = for (y <- 0 until level.height; x <- 0 until level.width) yield {
       val view = new ImageView(currTile.icon.croppedImage()) // TODO Tile with adjacent
       view.handleEvent(MouseEvent.Any) {
         e: MouseEvent => if (e.eventType == MouseEvent.MouseClicked) {
           val index = point2index(x, y)
-          tileSeq = tileSeq.updated(index, currTile)
+          level.tiles = level.tiles.updated(index, currTile)
           autotile(x, y)
         }
       }
       view
     }
-    gameMap.prefColumns = numCols
-    gameMap.prefRows = numRows
+    gameMap.children.clear()
+    gameMap.prefColumns = level.width
+    gameMap.prefRows = level.height
     gameMap.children ++= imgSeq.map(_.delegate)
     
-    for (y <- 0 until numRows; x <- 0 until numCols) {
+    for (y <- 0 until level.height; x <- 0 until level.width) {
       smoothTile(x, y)
     }
   }
   
   def autotile(x: Int, y: Int) = {
-    for (yy <- max(y - 1, 0) to min(y + 1, numCols - 1); xx <- max(x - 1, 0) to min(x + 1, numRows - 1)) {
+    for (yy <- max(y - 1, 0) to min(y + 1, level.height - 1); 
+        xx <- max(x - 1, 0) to min(x + 1, level.width - 1)) {
       smoothTile(xx, yy)
     }
   }
@@ -88,28 +125,74 @@ class MapBuilder {
     val dirs = scala.collection.mutable.Set[Direction.Value]()
     
     val onLeft = x == 0
-    val onRight = x + 1 == numCols
+    val onRight = x + 1 == level.width
     val onTop = y == 0
-    val onBottom = y + 1 == numRows
+    val onBottom = y + 1 == level.height
     
-    if (onLeft || tileSeq(left(i)) == tileSeq(i)) dirs += West
-    if (onRight || tileSeq(right(i)) == tileSeq(i)) dirs += East 
-    if (onTop || tileSeq(up(i)) == tileSeq(i)) dirs += North
-    if (onBottom || tileSeq(down(i)) == tileSeq(i)) dirs += South
-    if (onLeft || onTop || tileSeq(left(up(i))) == tileSeq(i)) dirs += Northwest
-    if (onLeft || onBottom || tileSeq(down(left(i))) == tileSeq(i)) dirs += Southwest
-    if (onTop || onRight || tileSeq(right(up(i))) == tileSeq(i)) dirs += Northeast
-    if (onBottom || onRight || tileSeq(right(down(i))) == tileSeq(i)) dirs += Southeast
+    if (onLeft || level.tiles(left(i)) == level.tiles(i)) dirs += West
+    if (onRight || level.tiles(right(i)) == level.tiles(i)) dirs += East 
+    if (onTop || level.tiles(up(i)) == level.tiles(i)) dirs += North
+    if (onBottom || level.tiles(down(i)) == level.tiles(i)) dirs += South
+    if (onLeft || onTop || level.tiles(left(up(i))) == level.tiles(i)) dirs += Northwest
+    if (onLeft || onBottom || level.tiles(down(left(i))) == level.tiles(i)) dirs += Southwest
+    if (onTop || onRight || level.tiles(right(up(i))) == level.tiles(i)) dirs += Northeast
+    if (onBottom || onRight || level.tiles(right(down(i))) == level.tiles(i)) dirs += Southeast
     val index = point2index(x, y) 
-    imgSeq(index).image = tileSeq(index).build(dirs.toSet).croppedImage()
+    imgSeq(index).image = level.tiles(index).build(dirs.toSet).croppedImage()
   }
   
-  def point2index(x: Int, y: Int) = y * numCols + x
+  def save() = {
+    if (lastFile != null) {
+      level saveTo lastFile
+    } else {
+      saveAs()
+    }  
+  }
+  
+  def saveAs() = {
+    val fileChooser = new FileChooser {
+      title = "Open Resource File"
+      initialDirectory = new File(lastDir())
+      extensionFilters ++= Seq(
+        new ExtensionFilter("MAP Files", "*.map"),
+        new ExtensionFilter("All Files", "*.*")
+      )
+    }
+    
+    val selectedFile = fileChooser.showSaveDialog(null)
+    if (selectedFile != null) {
+      rememberDir(selectedFile)
+      level saveTo selectedFile
+      lastFile = selectedFile
+    }
+  }
+  
+  def open() = {
+    val fileChooser = new FileChooser {
+      title = "Open Resource File"
+      initialDirectory = new File(lastDir())
+      extensionFilters ++= Seq(
+        new ExtensionFilter("MAP Files", "*.map"),
+        new ExtensionFilter("All Files", "*.*")
+      )
+    }
+    
+    val selectedFile = fileChooser.showOpenDialog(null)
+    if (selectedFile != null) {
+      rememberDir(selectedFile)
+      level = GameMap loadFrom selectedFile
+      tileset = level.tileset
+      setup()
+      lastFile = selectedFile
+    }
+  }
+  
+  def point2index(x: Int, y: Int) = level.pt2index(x, y)
   
   def left(i: Int) = i - 1
   def right(i: Int) = i + 1
-  def up(i: Int) = i - numCols
-  def down(i: Int) = i + numCols
+  def up(i: Int) = i - level.width
+  def down(i: Int) = i + level.width
 }
 
 object MapBuilder {
diff --git a/FakeMon/src/fmon/Config.scala b/FakeMon/src/fmon/Config.scala
index 7f9a5c3..455aa47 100644
--- a/FakeMon/src/fmon/Config.scala
+++ b/FakeMon/src/fmon/Config.scala
@@ -9,7 +9,8 @@ case class Config(
     val resistMult: Double,
     val weakMult: Double,
     val immuneMult: Double,
-    val maxBoost: Int
+    val maxBoost: Int,
+    val tileSize: Int
   ) {
   
 }
@@ -24,4 +25,5 @@ object Config {
   def weakMult = config.weakMult
   def immuneMult = config.immuneMult
   def maxBoost = config.maxBoost
+  def tileSize = config.tileSize
 }
\ No newline at end of file
diff --git a/FakeMon/src/fmon/config.yaml b/FakeMon/src/fmon/config.yaml
index 20a3e7e..a69fc53 100644
--- a/FakeMon/src/fmon/config.yaml
+++ b/FakeMon/src/fmon/config.yaml
@@ -6,3 +6,4 @@ resistMult: 0.5
 immuneMult: 0.0
 maxBoost: 6
 newday: 02:00
+tileSize: 32
diff --git a/FakeMon/src/fmon/draw/Palette.scala b/FakeMon/src/fmon/draw/Palette.scala
index 96acef0..5c2b169 100644
--- a/FakeMon/src/fmon/draw/Palette.scala
+++ b/FakeMon/src/fmon/draw/Palette.scala
@@ -1,6 +1,6 @@
 package fmon.draw
 
-import java.io.InputStream
+import java.io._
 
 import scalafx.geometry.Rectangle2D
 import scalafx.scene.image._
@@ -23,6 +23,7 @@ case class StillImg(val image: Image, val x: Double, val y: Double, val width: D
 class Palette(val image: Image, val numAcross: Int, val numDown: Int) {
   def this(url: String, numAcross: Int, numDown: Int) = this(new Image(url), numAcross, numDown)
   def this(stream: InputStream, numAcross: Int, numDown: Int) = this(new Image(stream), numAcross, numDown)
+  def this(file: File, numAcross: Int, numDown: Int) = this(new FileInputStream(file), numAcross, numDown)
   
   val imgWidth = image.width() / numAcross
   val imgHeight = image.height() / numDown
@@ -57,10 +58,14 @@ object Palette {
     new Palette(img, numAcross, numDown.toInt)
   }
   
-  def bySize(stream: InputStream, width: Double, height: Double) = {
+  def bySize(stream: InputStream, width: Double, height: Double): Palette = {
     val img = new Image(stream)
     val numAcross = img.width() / width
     val numDown = img.height() / height
     new Palette(img, numAcross.toInt, numDown.toInt)
   }
+  
+  def bySize(file: File, width: Double, height: Double): Palette = {
+    bySize(new FileInputStream(file), width, height)
+  }
 }
\ No newline at end of file
diff --git a/FakeMon/src/fmon/draw/tile/AutoFloorTile.scala b/FakeMon/src/fmon/draw/tile/AutoFloorTile.scala
index 7c8afcb..e774be0 100644
--- a/FakeMon/src/fmon/draw/tile/AutoFloorTile.scala
+++ b/FakeMon/src/fmon/draw/tile/AutoFloorTile.scala
@@ -12,7 +12,12 @@ import fmon.util._
 
 import Direction._
 
-class AutoFloorTile(val palette: Palette, val size: Int = 48) {
+trait AutoTile {
+  def icon: StillImg
+  def build(dirs: Set[Direction.Value]): StillImg
+}
+
+class AutoFloorTile(val palette: Palette, val size: Int = 48) extends AutoTile {
   
   def icon: StillImg = palette.apply(0, 0, 2, 2)
   
@@ -79,7 +84,7 @@ class AutoFloorTile(val palette: Palette, val size: Int = 48) {
   }
 }
 
-class AutoWallTile(val palette: Palette, val size: Int = 48) {
+class AutoWallTile(val palette: Palette, val size: Int = 48) extends AutoTile {
   def icon: StillImg = {
     val canvas = new scalafx.scene.canvas.Canvas(size, size)
     val g = canvas.graphicsContext2D
diff --git a/FakeMon/src/fmon/world/GameMap.scala b/FakeMon/src/fmon/world/GameMap.scala
new file mode 100644
index 0000000..870a432
--- /dev/null
+++ b/FakeMon/src/fmon/world/GameMap.scala
@@ -0,0 +1,41 @@
+package fmon.world
+
+import java.io._
+
+import fmon.draw.tile._
+import fmon.util._
+
+class GameMap(val width: Int, val height: Int, val tileset: Tileset) {
+  
+  var tiles: IndexedSeq[AutoTile] = for (y <- 0 until height; x <- 0 until width) yield tileset.groundTiles.head
+  
+  def saveTo(file: File): Unit = {
+    val ostream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))
+    ostream.writeInt(width)
+    ostream.writeInt(height)
+    ostream.writeObject(tileset.token.a2)// Tileset
+    val tileIndices = tileset.groundTiles.zipWithIndex.toMap
+    tiles.foreach(t => ostream.writeInt(tileIndices(t)))
+    //ostream.writeObject(tiles.map(tileIndices(_)).mkString(" "))
+    ostream.close()
+  }
+  
+  def pt2index(x: Int, y: Int) = y * width + x
+  def index2x(i: Int) = i % width
+  def index2y(i: Int) = i / width
+}
+
+object GameMap {
+  def loadFrom(file: File): GameMap = {
+    //val istream = new BufferedReader(new FileReader(file))
+    val istream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))
+    val width = istream.readInt()
+    val height = istream.readInt()
+    val tileset = new Tileset(new TilesetToken(istream.readObject().toString))
+    val indices = for (y <- 0 until height; x <- 0 until width) yield istream.readInt()
+    val map = new GameMap(width, height, tileset)
+    val tiles = indices.map(i => tileset.groundTiles(i))
+    map.tiles = tiles
+    map
+  }
+}
\ No newline at end of file
diff --git a/FakeMon/src/fmon/world/Tileset.scala b/FakeMon/src/fmon/world/Tileset.scala
new file mode 100644
index 0000000..2be00e6
--- /dev/null
+++ b/FakeMon/src/fmon/world/Tileset.scala
@@ -0,0 +1,18 @@
+package fmon.world
+
+import java.io._
+
+import fmon.Config
+import fmon.draw.tile._
+
+class Tileset(val token: TilesetToken) {
+  val groundTiles: IndexedSeq[AutoTile] = {
+    val file = new File(token.a2)
+    val palette = new AutoTilePalette(file, Config.tileSize)
+    for (y <- 0 until 4; x <- 0 until 8) yield {
+      palette(x, y)
+    }
+  }
+}
+
+case class TilesetToken(val a2: String)
\ No newline at end of file