-
-
Save hawkw/1a9b7363207f4fcab207a005bf27d71c to your computer and use it in GitHub Desktop.
A really dumb implementation of Minesweeper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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