Skip to content

Instantly share code, notes, and snippets.

@hawkw
Forked from ArcticLight/Minesweeper.scala
Last active December 14, 2016 20:43
Show Gist options
  • Save hawkw/1a9b7363207f4fcab207a005bf27d71c to your computer and use it in GitHub Desktop.
Save hawkw/1a9b7363207f4fcab207a005bf27d71c to your computer and use it in GitHub Desktop.
A really dumb implementation of Minesweeper
package me.arcticlight.minesweeper
import scala.annotation.tailrec
import scala.collection.immutable
import scala.language.postfixOps
import scala.io.StdIn
import scala.util.Try
/**
* Created by bogos on 12/13/2016.
*/
object Minesweeper {
// this might be more performant if it was an `Array`, but I wanted
// to make it immutable
type Board = IndexedSeq[IndexedSeq[Int]]
/** Utilities for multi-dimensional `IndexedSeq`s
* @param self
* @tparam A
*/
implicit class Rich2DSeq[A](val self: IndexedSeq[IndexedSeq[A]]) {
def update2d(x: Int, y: Int)(value: A): IndexedSeq[IndexedSeq[A]]
= self updated (x, self(x) updated (y, value) )
def map2d[B](f: (A) => B): IndexedSeq[IndexedSeq[B]]
= self map { _ map f }
final case class Index(x: Int, y: Int) {
def := (value: A): IndexedSeq[IndexedSeq[A]] = update2d(x, y)(value)
}
@inline final def index(x: Int)(y: Int): Rich2DSeq[A]#Index = Index(x, y)
final def apply(x: Int, y: Int): Rich2DSeq[A]#Index = index(x)(y)
}
implicit class RichBoard (val board: Board) {
private[this] val neighbors =
Seq((-1,-1),(0,-1),(1,-1),(-1,0),(1,0),(-1,1),(0,1),(1,1))
def calcNeighbors(x: Int, y: Int): Int =
(for { (i, j) <- neighbors
a <- board.lift(x + i)
b <- a.lift(y + j) }
yield b).count(_.isMine)
def show: String =
(for { (z, x) <- board.zipWithIndex }
yield (for { (v, y) <- z.zipWithIndex }
yield if (v > 0) calcNeighbors(x, y)
else "_") mkString ("["," ","]")
) mkString "\n"
//
// (board.zipWithIndex map { case (z,x) =>
// (z.zipWithIndex map {
// case (v, _) if v <= 0 => "X"
// case (_, y) => board calcNeighbors (x, y)
// }).mkString("["," ","]")
// }).mkString("\n")
/** Update the board by flipping over the square at (`x`, `y`),
* returning `Left` if the game continues, or `Right` if the game is over.
*/
val update: ((Int, Int)) => Either[Board, Board]
= { case (x, y) if board(x)(y) isMine => Right(board)
case (x, y) => Left(board(x, y) := 1)
}
def reveal: Board = board map2d { case 0 => 1; case y => y }
}
implicit class Square (val i: Int)
extends AnyVal {
/** @return true if this square is a mine */
@inline def isMine: Boolean = i == -1
}
/** Creates a new board.
*
* Optionally, takes parameters for the board size and number of mines.
*/
def makeBoard(dim: Int = 8, nMines: Int = 9): Board = {
import scala.util.Random
def randomPos(): Int = Math.abs(Random.nextInt()) % dim
(0 until nMines).foldRight(IndexedSeq.fill(8,8){0}) { (_, b: Board) =>
b(randomPos(), randomPos()) := -1
}
}
def main(args: Array[String]): Unit = {
@tailrec def _main(board: Board): Unit = {
println(board.show)
board.update(userInput(board.length)) match {
case Right(b2: Board) => println(s"You lose!\n${b2.reveal.show}")
case Left(b2: Board) => _main(b2)
}
}
// TODO: allow command-line options for variable board sizes
// and number of mines?
_main(makeBoard())
// var failed = false
// while(!failed) {
// println(board.show)
// val (x,y) = userInput()
// if( board(x)(y) isMine ) {
// println("You lose!")
// println( (board map { _ map { y => if(y == 0) 1 else y } } ).show )
// return
// }
// board(x)(y) = 1
// }
}
@tailrec def userInput(maxDim: Int): (Int, Int) = {
val lex: PartialFunction[Array[String], (String, String)] = {
case Array(x: String, y: String) => (x trim, y trim)
}
print("Selection: ")
val input = StdIn.readLine()
lex.lift(input.split(",")) flatMap { case (x: String, y: String) =>
Try((x toInt, y toInt)) toOption
} match {
case Some((x, _)) if x >= maxDim =>
println(s"X-coordinate $x exceeds size of board ($maxDim).")
userInput(maxDim)
case Some((_, y)) if y >= maxDim =>
println(s"y-coordinate $y exceeds size of board ($maxDim).")
userInput(maxDim)
case Some((x, y)) if x < 0 || y < 0 =>
println(s"Coordinates may not be less than 0.")
userInput(maxDim)
case Some(thing) => thing
case None =>
println(s"Invalid input $input. Please try again")
userInput(maxDim)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment