Minesweeper game/D: Difference between revisions
(Updated minesweeper_main1 D module) |
|||
(8 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
Phobos and Tango based implementations |
|||
== Phobos == |
|||
=== minesweeper_main1 module === |
|||
<lang d>module minesweeper_main1; |
|||
import std.stdio, std.conv, std.array, std.string, std.typecons, |
|||
minesweeper1; |
|||
Tuple!(uint,"height", uint,"width") getBoardSides() { |
|||
while (true) { |
|||
write("Give me height width (2 2 or more): "); |
|||
const parts = readln().split(); |
|||
if (parts.length != 2) { |
|||
writeln("Required: number number"); |
|||
continue; |
|||
} |
|||
try { |
|||
immutable height = to!uint(parts[0]); |
|||
immutable width = to!uint(parts[1]); |
|||
if (height < 2 || width < 2) { |
|||
writeln("Minimal board sides: 2 2"); |
|||
continue; |
|||
} |
|||
return typeof(return)(height, width); |
|||
} catch (ConvException e) { |
|||
writeln("Invalid input. Required: number number"); |
|||
} |
|||
} |
|||
} |
|||
void main() { |
|||
enum Action { uncover, flag, clear } |
|||
static immutable string prompt = |
|||
"\n[Flag toggle|Clear] nRow nCol (or Quit): "; |
|||
writeln("Welcome to Minesweeper Game!"); |
|||
immutable sides = getBoardSides(); |
|||
auto board = new GameBoard(sides.height, sides.width); |
|||
writeln("Mines to find: ", board.nMines); |
|||
while (true) { |
|||
int row, column; |
|||
Action action; |
|||
while (true) { |
|||
writeln(board); |
|||
row = column = -1; |
|||
write(prompt); |
|||
auto parts = readln().toLower().split(); |
|||
if (parts.empty) |
|||
continue; |
|||
if (parts[0] == "quit" || parts[0] == "q") |
|||
return; |
|||
action = Action.uncover; |
|||
if (parts[0] == "flag" || parts[0] == "f") |
|||
action = Action.flag; |
|||
else if (parts[0] == "clear" || parts[0] == "c") |
|||
action = Action.clear; |
|||
if (action != Action.uncover) |
|||
parts.popFront(); |
|||
if (parts.length != 2) { |
|||
writeln("Invalid input."); |
|||
continue; |
|||
} |
|||
try { |
|||
row = to!int(parts[0]) - 1; |
|||
column = to!int(parts[1]) - 1; |
|||
} catch (ConvException e) { |
|||
writeln("Invalid input."); |
|||
continue; |
|||
} |
|||
if (row < 0 || row >= board.nRows || |
|||
column < 0 || column >= board.nColumns) { |
|||
writeln("Invalid row col bounds."); |
|||
continue; |
|||
} |
|||
break; |
|||
} |
|||
GameBoard.Cell cell = board[row, column]; |
|||
if (action == Action.flag) { |
|||
cell.flag(); |
|||
} else { |
|||
(action == Action.clear ? &cell.clear : &cell.uncover)(); |
|||
if (board.state != GameBoard.State.ongoing) { |
|||
writeln((board.state == GameBoard.State.win) ? |
|||
"You Win!" : "BIG BADA BOOM!"); |
|||
writeln(board); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
}</lang> |
|||
=== minesweeper1 module === |
|||
<lang d>module minesweeper1; |
|||
import std.random, std.conv, std.string, std.array, std.range; |
|||
final class GameBoard { |
|||
public: |
|||
enum State { ongoing, win, loss } |
|||
this(in int n_rows_, in int n_columns_) |
|||
in { |
|||
assert(n_rows_ > 1 && n_columns_ > 1); |
|||
} body { |
|||
this.n_rows = n_rows_; |
|||
this.n_columns = n_columns_; |
|||
cells = new typeof(cells)(n_rows, n_columns); |
|||
foreach (row; cells) |
|||
foreach (ref cell; row) |
|||
cell = new typeof(cell)(); |
|||
placeMines(); |
|||
setAdjacentCells(); |
|||
} |
|||
@property uint nRows() pure nothrow const { return n_rows; } |
|||
@property uint nColumns() pure nothrow const { return n_columns; } |
|||
@property State state() pure nothrow const { return status; } |
|||
@property uint nMines() pure nothrow const { return n_mines; } |
|||
inout opIndex(in uint row, in uint column) const pure nothrow |
|||
in { |
|||
assert(row < n_rows && column < n_columns, |
|||
"Incorrect dimensions for cell retrieval!"); |
|||
} body { |
|||
return cells[row][column]; |
|||
} |
|||
override string toString() const { |
|||
string result = format("N. flags: %d\n %(%d%)", |
|||
n_flags, iota(1, n_columns + 1)); |
|||
foreach (immutable r; 0 .. n_rows) { |
|||
string row; |
|||
foreach (immutable c; 0 .. n_columns) |
|||
row ~= this[r, c].toString(); |
|||
result ~= format("\n%2d [%s]", r + 1, row); |
|||
} |
|||
return result; |
|||
} |
|||
private: |
|||
void placeMines() { |
|||
n_mines = cast(uint)(n_rows * n_columns * uniform(0.1, 0.2)); |
|||
n_mines++; |
|||
foreach (immutable i; 0 .. n_mines) { |
|||
while (true) { |
|||
auto cell = cells[uniform(0, n_rows)] |
|||
[uniform(0, n_columns)]; |
|||
if (!cell.isMined) { |
|||
cell.isMined = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
void setAdjacentCells() pure nothrow { |
|||
foreach (immutable row; 0 .. n_rows) { |
|||
immutable uint isTop = row == 0, |
|||
isBottom = row == (n_rows - 1); |
|||
foreach (immutable column; 0 .. n_columns) { |
|||
immutable uint isLeft = column == 0, |
|||
isRight = column == (n_columns - 1); |
|||
auto cell = cells[row][column]; |
|||
foreach (immutable r; row - !isTop |
|||
.. row + 1 + !isBottom) |
|||
foreach (immutable c; column - !isLeft |
|||
.. column + 1 + !isRight) |
|||
if (r != row || c != column) { |
|||
cell.adjacents ~= cells[r][c]; |
|||
cell.numAdjacentMines += |
|||
cell.adjacents.back.isMined; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
void revealAll() pure nothrow { |
|||
foreach (row; cells) |
|||
foreach (cell; row) { |
|||
cell.isFlagged = false; |
|||
cell.isUncovered = true; |
|||
} |
|||
} |
|||
immutable uint n_rows, n_columns; |
|||
Cell[][] cells; |
|||
uint n_flags, n_mines, n_uncovered; |
|||
State status; |
|||
public: |
|||
final class Cell { |
|||
public: |
|||
void uncover() pure nothrow { |
|||
if (isUncovered || isFlagged) |
|||
return; |
|||
isUncovered = true; |
|||
if (isMined) { |
|||
status = State.loss; |
|||
revealAll(); |
|||
return; |
|||
} |
|||
n_uncovered++; |
|||
if (n_uncovered + n_mines == n_rows * n_columns) { |
|||
status = State.win; |
|||
return; |
|||
} |
|||
if (numAdjacentMines > 0) |
|||
return; |
|||
foreach (cell; adjacents) |
|||
cell.uncover(); |
|||
} |
|||
void flag() pure nothrow { |
|||
if (isUncovered) |
|||
return; |
|||
isFlagged = isFlagged != true; |
|||
n_flags += isFlagged ? +1 : -1; |
|||
} |
|||
void clear() pure nothrow { |
|||
if (!isUncovered) |
|||
return; |
|||
uint numFlagged; |
|||
foreach (cell; adjacents) |
|||
numFlagged += cell.isFlagged; |
|||
if (numFlagged == numAdjacentMines && |
|||
status == State.ongoing) |
|||
foreach (cell; adjacents) |
|||
cell.uncover(); |
|||
} |
|||
override string toString() const pure nothrow { |
|||
if (isUncovered) { |
|||
if (isMined) |
|||
return "*"; |
|||
else |
|||
return numAdjacentMines |
|||
? text(numAdjacentMines) |
|||
: " "; |
|||
} else { |
|||
return isFlagged ? "?" : "."; |
|||
} |
|||
} |
|||
private: |
|||
Cell[] adjacents; |
|||
bool isMined, isUncovered, isFlagged; |
|||
uint numAdjacentMines; |
|||
} |
|||
} |
|||
void main(){}</lang> |
|||
=== Example plays === |
|||
==== lost game ==== |
|||
<pre>Welcome to Minesweeper Game! |
|||
Give me height width (2 2 or more): 4 6 |
|||
Mines to find: 5 |
|||
N. flags: 0 |
|||
123456 |
|||
1 [......] |
|||
2 [......] |
|||
3 [......] |
|||
4 [......] |
|||
[Flag toggle|Clear] nRow nCol (or Quit): 1 1 |
|||
N. flags: 0 |
|||
123456 |
|||
1 [2.....] |
|||
2 [......] |
|||
3 [......] |
|||
4 [......] |
|||
[Flag toggle|Clear] nRow nCol (or Quit): 1 2 |
|||
N. flags: 0 |
|||
123456 |
|||
1 [23....] |
|||
2 [......] |
|||
3 [......] |
|||
4 [......] |
|||
[Flag toggle|Clear] nRow nCol (or Quit): 1 3 |
|||
BIG BADA BOOM! |
|||
N. flags: 0 |
|||
123456 |
|||
1 [23*1 ] |
|||
2 [**3321] |
|||
3 [222**1] |
|||
4 [ 1221]</pre> |
|||
== Tango == |
== Tango == |
||
Line 24: | Line 335: | ||
height = Int.parse (prompt, 10, &len1); |
height = Int.parse (prompt, 10, &len1); |
||
width = Int.parse (prompt[len1..$], 10, &len2); |
width = Int.parse (prompt[len1..$], 10, &len2); |
||
do { |
do { |
||
mines = rand.next(cast(uint)( width * height * 0.2)); |
mines = rand.next(cast(uint)( width * height * 0.2)); |
||
} while (mines <= 1); |
} while (mines <= 1); |
||
Line 87: | Line 398: | ||
} |
} |
||
</lang> |
</lang> |
||
=== Example plays === |
|||
==== won game ==== |
|||
Welcome! |
|||
Gimme height width: |
|||
:> 4 6 |
|||
00 123456 |
|||
1 [ ...... ] |
|||
2 [ ...... ] |
|||
3 [ ...... ] |
|||
4 [ ...... ] |
|||
:> 2 2 |
|||
00 123456 |
|||
1 [ ...... ] |
|||
2 [ .1.... ] |
|||
3 [ ...... ] |
|||
4 [ ...... ] |
|||
:> 3 3 |
|||
00 123456 |
|||
1 [ ...... ] |
|||
2 [ .11121 ] |
|||
3 [ 11____ ] |
|||
4 [ ______ ] |
|||
:> F 2 1 |
|||
01 123456 |
|||
1 [ ...... ] |
|||
2 [ ?11121 ] |
|||
3 [ 11____ ] |
|||
4 [ ______ ] |
|||
:> 1 3 |
|||
01 123456 |
|||
1 [ ..1... ] |
|||
2 [ ?11121 ] |
|||
3 [ 11____ ] |
|||
4 [ ______ ] |
|||
:> 1 5 |
|||
01 123456 |
|||
1 [ ..1.2. ] |
|||
2 [ ?11121 ] |
|||
3 [ 11____ ] |
|||
4 [ ______ ] |
|||
:> 1 1 |
|||
01 123456 |
|||
1 [ 1.1.2. ] |
|||
2 [ ?11121 ] |
|||
3 [ 11____ ] |
|||
4 [ ______ ] |
|||
:> 1 2 |
|||
KESSETOUN!!! |
|||
01 123456 |
|||
1 [ 111.2. ] |
|||
2 [ ?11121 ] |
|||
3 [ 11____ ] |
|||
4 [ ______ ] |
|||
==== lost game ==== |
|||
Welcome! |
|||
Gimme height width: |
|||
:> 4 6 |
|||
00 123456 |
|||
1 [ ...... ] |
|||
2 [ ...... ] |
|||
3 [ ...... ] |
|||
4 [ ...... ] |
|||
:> 1 1 |
|||
00 123456 |
|||
1 [ ___1.. ] |
|||
2 [ ___1.. ] |
|||
3 [ _112.. ] |
|||
4 [ _1.... ] |
|||
:> 1 5 |
|||
BIG BADA BOOM |
|||
00 123456 |
|||
1 [ ___1*. ] |
|||
2 [ ___1.. ] |
|||
3 [ _112.. ] |
|||
4 [ _1.... ] |
|||
=== MineSweeper module === |
=== MineSweeper module === |
||
Line 183: | Line 416: | ||
int value; |
int value; |
||
Field[] friends; |
Field[] friends; |
||
} |
} |
||
void addFriend(Field fr) { friends ~= fr; } |
void addFriend(Field fr) { friends ~= fr; } |
||
Line 402: | Line 635: | ||
} |
} |
||
} |
} |
||
} |
|||
</lang> |
|||
== Phobos == |
|||
=== Main module === |
|||
<lang D>module main; |
|||
import std.stdio; |
|||
import std.format; |
|||
import std.conv; |
|||
import std.array; |
|||
import std.string; |
|||
import core.thread; |
|||
import core.time; |
|||
import minesweeper; |
|||
//Only works in Windows. A portable solution is a bit more complex. |
|||
//See http://stackoverflow.com/a/5383993. |
|||
extern(C) int kbhit(); |
|||
void main() { |
|||
PlayMinesweeper(); |
|||
while(!kbhit()) Thread.sleep(dur!"msecs"(250)); |
|||
} |
|||
void PlayMinesweeper() { |
|||
static string prompt = ":> "; |
|||
uint len1, height, width; |
|||
string curLine; |
|||
bool gameOver; |
|||
writeln("Welcome!"); |
|||
do { |
|||
write("Gimme height width:\n", prompt); |
|||
curLine = readln(); |
|||
//Non-member functions can be called with the first argument using dot notation. |
|||
//"curLine.formattedRead(" is the same as "formattedRead(curLine, ". |
|||
len1 = curLine.formattedRead("%s %s", &height, &width); |
|||
} while (len1 == 0 || height < 2 || width < 2); |
|||
auto gameBoard = new Board(height, width); |
|||
writeln(gameBoard); |
|||
do { |
|||
int row, column; |
|||
bool print, flag, clear; |
|||
do { |
|||
row = column = -1; |
|||
write(prompt); |
|||
curLine = readln(); |
|||
if(curLine.strip() == "quit") |
|||
return; |
|||
string[] tokens = curLine.split(); |
|||
flag = tokens[0] == "F"; |
|||
print = tokens[0] == "P"; |
|||
clear = tokens[0] == "C"; |
|||
if(print && tokens.length == 1) |
|||
{ |
|||
writeln(gameBoard); |
|||
continue; |
|||
} |
|||
try { |
|||
auto index = flag || print || clear; |
|||
row = to!int(tokens[index]) - 1; |
|||
column = to!int(tokens[index + 1]) - 1; |
|||
} |
|||
catch(Exception e) { |
|||
writeln("Invalid input"); |
|||
continue; |
|||
} |
|||
} while(row < 0 || row >= gameBoard.Rows || column < 0 || column >= gameBoard.Columns); |
|||
auto cell = gameBoard.getCell(row, column); |
|||
if(flag) cell.flag(); |
|||
else { |
|||
(clear ? &cell.clear : &cell.uncover)(); |
|||
gameOver = gameBoard.Status != Board.DEFAULT; |
|||
if(gameOver) |
|||
writeln((gameBoard.Status == Board.WIN) ? |
|||
"DEAR DIARY... JACKPOT!" : "BIG BADA BOOM"); |
|||
} |
|||
if(print || gameOver) writeln(gameBoard); |
|||
} while(!gameOver); |
|||
} |
} |
||
</lang> |
</lang> |
||
Line 499: | Line 642: | ||
==== won game ==== |
==== won game ==== |
||
Welcome! |
Welcome! |
||
Gimme height width: |
Gimme height width: |
||
:> 4 6 |
:> 4 6 |
||
00 123456 |
|||
1 [ ...... ] |
|||
2 [ ...... ] |
|||
3 [ ...... ] |
|||
4 [ ...... ] |
|||
:> |
:> 2 2 |
||
00 123456 |
|||
1 [ ...... ] |
|||
2 [ .1.... ] |
|||
3 [ ...... ] |
|||
4 [ ...... ] |
|||
:> |
:> 3 3 |
||
00 123456 |
|||
:> F 3 4 |
|||
1 [ ...... ] |
|||
:> F 3 6 |
|||
2 [ .11121 ] |
|||
:> 3 1 |
|||
3 [ 11____ ] |
|||
:> C 2 4 |
|||
4 [ ______ ] |
|||
:> P |
|||
:> F 2 1 |
|||
01 123456 |
|||
1 [ ...... ] |
|||
2 [ ?11121 ] |
|||
3 [ 11____ ] |
|||
4 [ ______ ] |
|||
:> C 3 3 |
|||
:> |
:> 1 3 |
||
01 123456 |
|||
:> P |
|||
1 [ ..1... ] |
|||
03 123456 |
|||
2 [ ?11121 ] |
|||
3 [ 11____ ] |
|||
4 [ ______ ] |
|||
:> 1 5 |
|||
01 123456 |
|||
:> 4 6 |
|||
1 [ ..1.2. ] |
|||
DEAR DIARY... JACKPOT! |
|||
2 [ ?11121 ] |
|||
03 123456 |
|||
3 [ 11____ ] |
|||
4 [ ______ ] |
|||
:> 1 1 |
|||
01 123456 |
|||
4 [ 1122.2 ] |
|||
1 [ 1.1.2. ] |
|||
2 [ ?11121 ] |
|||
3 [ 11____ ] |
|||
4 [ ______ ] |
|||
:> 1 2 |
|||
KESSETOUN!!! |
|||
01 123456 |
|||
1 [ 111.2. ] |
|||
2 [ ?11121 ] |
|||
3 [ 11____ ] |
|||
4 [ ______ ] |
|||
==== lost game ==== |
==== lost game ==== |
||
Welcome! |
Welcome! |
||
Gimme height width: |
Gimme height width: |
||
:> 4 6 |
:> 4 6 |
||
00 123456 |
|||
1 [ ...... ] |
|||
2 [ ...... ] |
|||
3 [ ...... ] |
|||
4 [ ...... ] |
|||
:> |
:> 1 1 |
||
00 123456 |
|||
1 [ ___1.. ] |
|||
2 [ ___1.. ] |
|||
3 [ _112.. ] |
|||
4 [ _1.... ] |
|||
:> |
:> 1 5 |
||
BIG BADA BOOM |
|||
00 123456 |
|||
00 123456 |
|||
1 [ 11.... ] |
|||
1 [ ___1*. ] |
|||
2 [ ___1.. ] |
|||
3 [ _112.. ] |
|||
4 [ _1.... ] |
|||
:> 2 3 |
|||
:> 1 3 |
|||
:> P |
|||
00 123456 |
|||
1 [ 11 1. ] |
|||
2 [ .1 11 ] |
|||
3 [ .321 ] |
|||
4 [ ...1 ] |
|||
:> 1 6 |
|||
BIG BADA BOOM |
|||
00 123456 |
|||
1 [ 11 1* ] |
|||
2 [ *1 11 ] |
|||
3 [ 2321 ] |
|||
4 [ 1**1 ] |
|||
=== MineSweeper module === |
|||
<lang D>module minesweeper; |
|||
import std.random; |
|||
import std.conv; |
|||
import std.format; |
|||
import std.algorithm; |
|||
import std.range; |
|||
//Convenience function. Similar to Python's str.format. |
|||
string format(Args...)(string fmt, Args args) |
|||
{ |
|||
auto writer = appender!string(""); |
|||
writer.formattedWrite(fmt, args); |
|||
return writer.data(); |
|||
} |
|||
class Board { |
|||
public: |
|||
static enum { DEFAULT, WIN, LOSS }; |
|||
this(int rows, int columns) { |
|||
this.rows = rows; |
|||
this.columns = columns; |
|||
cells = new typeof(cells)(rows, columns); |
|||
foreach(ref row; cells) |
|||
foreach(ref cell; row) |
|||
cell = new typeof(cell)(); |
|||
placeMines(); |
|||
setAdjacentCells(); |
|||
} |
|||
@property const uint Rows() { return rows; } |
|||
@property const uint Columns() { return columns; } |
|||
@property const uint Status() { return status; } |
|||
string toString() { |
|||
auto columnRange = iota(0, columns), rowRange = iota(0, rows); |
|||
auto accColumn = (string acc, uint column) { return acc ~ to!string(column + 1); }, |
|||
accLine = (string acc, uint row) { |
|||
auto accCell = (string acc, uint column) { return acc ~ getCell(row, column).toString(); }; |
|||
return acc ~ "\n%2s [ %s ]".format(row + 1, reduce!accCell("", columnRange)); |
|||
}; |
|||
return " %02s %s".format(numFlags, reduce!accColumn("", columnRange)) ~ reduce!accLine("", rowRange); |
|||
} |
|||
auto getCell(uint row, uint column) { |
|||
if((row >= rows) || (column >= columns)) |
|||
throw new Exception("Incorrect dimensions for cell retrieval!"); |
|||
return cells[row][column]; |
|||
} |
|||
private: |
|||
void setAdjacentCells() { |
|||
foreach(row; 0..rows) { |
|||
uint isTop = row == 0, |
|||
isBottom = row == (rows - 1); |
|||
foreach(column; 0..columns) { |
|||
uint isLeft = column == 0, |
|||
isRight = column == (columns - 1); |
|||
auto cell = cells[row][column]; |
|||
auto adjacent = new Cell[](0); |
|||
foreach(r; row - !isTop..row + 1 + !isBottom) |
|||
foreach(c; column - !isLeft..column + 1 + !isRight) |
|||
if((r != row) || (c != column)) { |
|||
adjacent ~= cells[r][c]; |
|||
cell.numAdjacentMines += adjacent.back.isMined; |
|||
} |
|||
cell.adjacent = adjacent; |
|||
} |
|||
} |
|||
} |
|||
void placeMines() { |
|||
auto randGen = Random(unpredictableSeed); |
|||
do { |
|||
numMines = cast(uint)(rows*columns*uniform(0.1, 0.2, randGen)); |
|||
} while (numMines <= 1); |
|||
foreach(i; 0..numMines) { |
|||
uint rand = 0; |
|||
Cell cell; |
|||
do { |
|||
rand = uniform(uint.min, rows * columns, randGen); |
|||
cell = cells[rand/columns][rand%columns]; |
|||
} while(cell.isMined); |
|||
cell.isMined = true; |
|||
} |
|||
} |
|||
void reveal() { |
|||
foreach(row; cells) |
|||
foreach(cell; row) { |
|||
cell.isFlagged = false; |
|||
cell.isUncovered = true; |
|||
} |
|||
} |
|||
Cell[][] cells; |
|||
const uint rows, columns; |
|||
uint numFlags, numMines, numUncovered, status; |
|||
public: |
|||
class Cell { |
|||
public: |
|||
void flag() { |
|||
if(isUncovered) return; |
|||
isFlagged = (isFlagged != true); |
|||
numFlags += isFlagged ? 1 : -1; |
|||
} |
|||
void uncover() { |
|||
if(isUncovered || isFlagged) return; |
|||
isUncovered = true; |
|||
if(isMined) { |
|||
status = LOSS; |
|||
reveal(); |
|||
return; |
|||
} |
|||
if(++numUncovered + numMines == rows * columns) |
|||
{ |
|||
status = WIN; |
|||
return; |
|||
} |
|||
if(numAdjacentMines > 0) |
|||
return; |
|||
foreach(cell; adjacent) |
|||
cell.uncover(); |
|||
} |
|||
void clear() { |
|||
if(!isUncovered) return; |
|||
uint numFlagged; |
|||
foreach(cell; adjacent) |
|||
numFlagged += cell.isFlagged; |
|||
if(numFlagged == numAdjacentMines) |
|||
foreach(cell; adjacent) if(status == DEFAULT) |
|||
cell.uncover(); |
|||
} |
|||
string toString() { |
|||
return isUncovered ? (isMined ? "*" : (numAdjacentMines ? to!string(numAdjacentMines) : " ")) : |
|||
(isFlagged ? "?" : "."); |
|||
} |
|||
private: |
|||
Cell[] adjacent; |
|||
bool isMined, isUncovered, isFlagged; |
|||
ushort numAdjacentMines; |
|||
} |
|||
} |
|||
</lang> |
Latest revision as of 18:39, 27 January 2015
Phobos and Tango based implementations
Phobos
minesweeper_main1 module
<lang d>module minesweeper_main1;
import std.stdio, std.conv, std.array, std.string, std.typecons,
minesweeper1;
Tuple!(uint,"height", uint,"width") getBoardSides() {
while (true) { write("Give me height width (2 2 or more): "); const parts = readln().split(); if (parts.length != 2) { writeln("Required: number number"); continue; }
try { immutable height = to!uint(parts[0]); immutable width = to!uint(parts[1]);
if (height < 2 || width < 2) { writeln("Minimal board sides: 2 2"); continue; }
return typeof(return)(height, width); } catch (ConvException e) { writeln("Invalid input. Required: number number"); } }
}
void main() {
enum Action { uncover, flag, clear } static immutable string prompt = "\n[Flag toggle|Clear] nRow nCol (or Quit): ";
writeln("Welcome to Minesweeper Game!"); immutable sides = getBoardSides(); auto board = new GameBoard(sides.height, sides.width); writeln("Mines to find: ", board.nMines);
while (true) { int row, column; Action action;
while (true) { writeln(board); row = column = -1; write(prompt); auto parts = readln().toLower().split(); if (parts.empty) continue;
if (parts[0] == "quit" || parts[0] == "q") return; action = Action.uncover; if (parts[0] == "flag" || parts[0] == "f") action = Action.flag; else if (parts[0] == "clear" || parts[0] == "c") action = Action.clear; if (action != Action.uncover) parts.popFront(); if (parts.length != 2) { writeln("Invalid input."); continue; }
try { row = to!int(parts[0]) - 1; column = to!int(parts[1]) - 1; } catch (ConvException e) { writeln("Invalid input."); continue; }
if (row < 0 || row >= board.nRows || column < 0 || column >= board.nColumns) { writeln("Invalid row col bounds."); continue; }
break; }
GameBoard.Cell cell = board[row, column];
if (action == Action.flag) { cell.flag(); } else { (action == Action.clear ? &cell.clear : &cell.uncover)(); if (board.state != GameBoard.State.ongoing) { writeln((board.state == GameBoard.State.win) ? "You Win!" : "BIG BADA BOOM!"); writeln(board); break; } } }
}</lang>
minesweeper1 module
<lang d>module minesweeper1;
import std.random, std.conv, std.string, std.array, std.range;
final class GameBoard { public:
enum State { ongoing, win, loss }
this(in int n_rows_, in int n_columns_) in { assert(n_rows_ > 1 && n_columns_ > 1); } body { this.n_rows = n_rows_; this.n_columns = n_columns_; cells = new typeof(cells)(n_rows, n_columns);
foreach (row; cells) foreach (ref cell; row) cell = new typeof(cell)(); placeMines(); setAdjacentCells(); }
@property uint nRows() pure nothrow const { return n_rows; } @property uint nColumns() pure nothrow const { return n_columns; } @property State state() pure nothrow const { return status; } @property uint nMines() pure nothrow const { return n_mines; }
inout opIndex(in uint row, in uint column) const pure nothrow in { assert(row < n_rows && column < n_columns, "Incorrect dimensions for cell retrieval!"); } body { return cells[row][column]; }
override string toString() const { string result = format("N. flags: %d\n %(%d%)", n_flags, iota(1, n_columns + 1));
foreach (immutable r; 0 .. n_rows) { string row; foreach (immutable c; 0 .. n_columns) row ~= this[r, c].toString(); result ~= format("\n%2d [%s]", r + 1, row); }
return result; }
private:
void placeMines() { n_mines = cast(uint)(n_rows * n_columns * uniform(0.1, 0.2)); n_mines++;
foreach (immutable i; 0 .. n_mines) { while (true) { auto cell = cells[uniform(0, n_rows)] [uniform(0, n_columns)]; if (!cell.isMined) { cell.isMined = true; break; } } } }
void setAdjacentCells() pure nothrow { foreach (immutable row; 0 .. n_rows) { immutable uint isTop = row == 0, isBottom = row == (n_rows - 1);
foreach (immutable column; 0 .. n_columns) { immutable uint isLeft = column == 0, isRight = column == (n_columns - 1); auto cell = cells[row][column];
foreach (immutable r; row - !isTop .. row + 1 + !isBottom) foreach (immutable c; column - !isLeft .. column + 1 + !isRight) if (r != row || c != column) { cell.adjacents ~= cells[r][c]; cell.numAdjacentMines += cell.adjacents.back.isMined; } } } }
void revealAll() pure nothrow { foreach (row; cells) foreach (cell; row) { cell.isFlagged = false; cell.isUncovered = true; } }
immutable uint n_rows, n_columns; Cell[][] cells; uint n_flags, n_mines, n_uncovered; State status;
public:
final class Cell { public: void uncover() pure nothrow { if (isUncovered || isFlagged) return; isUncovered = true;
if (isMined) { status = State.loss; revealAll(); return; } n_uncovered++; if (n_uncovered + n_mines == n_rows * n_columns) { status = State.win; return; } if (numAdjacentMines > 0) return;
foreach (cell; adjacents) cell.uncover(); }
void flag() pure nothrow { if (isUncovered) return; isFlagged = isFlagged != true; n_flags += isFlagged ? +1 : -1; }
void clear() pure nothrow { if (!isUncovered) return;
uint numFlagged; foreach (cell; adjacents) numFlagged += cell.isFlagged;
if (numFlagged == numAdjacentMines && status == State.ongoing) foreach (cell; adjacents) cell.uncover(); }
override string toString() const pure nothrow { if (isUncovered) { if (isMined) return "*"; else return numAdjacentMines ? text(numAdjacentMines) : " "; } else { return isFlagged ? "?" : "."; } }
private: Cell[] adjacents; bool isMined, isUncovered, isFlagged; uint numAdjacentMines; }
}
void main(){}</lang>
Example plays
lost game
Welcome to Minesweeper Game! Give me height width (2 2 or more): 4 6 Mines to find: 5 N. flags: 0 123456 1 [......] 2 [......] 3 [......] 4 [......] [Flag toggle|Clear] nRow nCol (or Quit): 1 1 N. flags: 0 123456 1 [2.....] 2 [......] 3 [......] 4 [......] [Flag toggle|Clear] nRow nCol (or Quit): 1 2 N. flags: 0 123456 1 [23....] 2 [......] 3 [......] 4 [......] [Flag toggle|Clear] nRow nCol (or Quit): 1 3 BIG BADA BOOM! N. flags: 0 123456 1 [23*1 ] 2 [**3321] 3 [222**1] 4 [ 1221]
Tango
Main module
<lang D>import tango.io.Stdout; import tango.io.Console; import Int = tango.text.convert.Integer; import tango.math.random.Random;
import MineSweeper;
void main() {
uint len1, len2; uint height, width, mines; bool gameOver=false;
Stdout ("Welcome!").newline; do { Stdout ("Gimme height width: ").newline; len1=len2=0;
auto prompt = Cin.get; height = Int.parse (prompt, 10, &len1); width = Int.parse (prompt[len1..$], 10, &len2); do { mines = rand.next(cast(uint)( width * height * 0.2)); } while (mines <= 1);
debug Stdout(height, width, mines, width*height).newline; } while (len1 <= 0 || len2 <= 0 || height < 2 || width < 2);
auto miner = new MineSweeper(height, width, mines); char[] prompt; bool changed; do { int i=0; Stdout(miner.getVisibles).newline; foreach(j, row; miner) { if (!i) { Stdout.format (" {:d2} ", miner.getFlaggedCount); for (i=0; i<miner.getWidth; i++) Stdout.format ("{}", (i+1) % 10); Stdout.newline; } Stdout.format (" {} [ ", (j+1) % 10) (row); Stdout (" ] ").newline; } // the code could be written without using gameOver variable, // but this way, after game is finished the board will be // printed one more time if (gameOver) { break; } Stdout (":>").flush; prompt=Cin.get;
bool flag = (prompt[0] == 'F'); if (flag) { prompt = prompt[1..$]; }
len1=len2=0; auto y = Int.parse (prompt, 10, &len1); auto x = Int.parse (prompt[len1..$], 10, &len2); assert (y < 1 || y > miner.getHeight); assert (x < 1 || x > miner.getWidth); --x; --y; if (len1 && len2) { int retcode = flag ? miner.flag(changed, y,x) : miner.dig(changed, y, x);
switch (retcode) { case -1: Stdout ("BIG BADA BOOM").newline; gameOver=true; break; case 1: Stdout ("KESSETOUN!!!").newline; gameOver=true; break; default: break; } }
} while (prompt != "quit");
} </lang>
MineSweeper module
<lang D>module MineSweeper;
import tango.math.random.Random; import tango.math.random.engines.Twister; import Int = tango.text.convert.Integer;
import tango.io.Stdout;
class MineSweeper {
class Field {/*{{{*/ private { enum State { UNKNOWN, VISIBLE, FLAGGED }; State state; bool hasBomb; int value; Field[] friends; }
void addFriend(Field fr) { friends ~= fr; } void addFriends(Field[] fr) { friends = fr; } void mineArea() { hasBomb = true; } bool isMined() { return hasBomb; } bool isVisible() { return state==State.VISIBLE; }
private { void updateValue() { value = 0; foreach (ref l; friends) if (l.isMined) value++; }
int dig() { bool dangerousFriends=false;
if (state == State.VISIBLE) return 0;
if (state == State.FLAGGED) { flag; return 0; }
state = State.VISIBLE;
if (hasBomb) return -1;
this.outer.visibles++;
foreach (ref fr; friends) if (fr.isMined) { dangerousFriends=true; break; }
if (!dangerousFriends) { foreach (ref fr; friends) if (! fr.isVisible) fr.dig; } return 0; }
void flag() { if (state == State.VISIBLE) return; state ^= State.FLAGGED; this.outer.flaggedCount += state - 1; // ;>
if (!hasBomb) this.outer.visibles += state - 1; } }
char[] toString() { const char[][] status = [ ".", "blurp", "?" ]; return (state==State.VISIBLE?(hasBomb?"*":(value?Int.toString(value):"_")):status[state])~" "; } }/*}}}*/
/* this is only wrapper for opCall(), * why the board isn't created as array of rows instead of 2D array? * simple, because opArray* methods would be necessary * and that would give access to elements in * foreach(blah; MineSweeperObj).. */ class RowWrapper { private { Field[] row; char[] row_str; } this(Field[] r) { row = r; row_str = new char[r.length]; } char[] toString() { foreach (uint a, b; row) row_str[a] = b.toString[0]; return row_str; } }
private { Field[][] board; RowWrapper[] rows; int y, x, mines; int flaggedCount; int visibles; }
this (int y = 10, int x = 10, int mines = 10) { assert (x >= 2 && y >= 2, "wrong dimensions");
board = new Field[][](y,x); rows = new RowWrapper[](y);
for (auto j=0; j<y; j++) { for (auto i=0; i<x; i++) board[j][i] = new Field; rows[j] = new RowWrapper(board[j]); }
this.y = y; this.x = x; this.mines = mines; createNeighborhood; createLandMines; updateValues; }
private { void createNeighborhood() { this.mines = mines;
/* now don't look at the code below, * it's evil :P */ /* middle */ for (auto j=1; j<y-1; j++) for (auto i=1; i<x-1; i++) board[j][i].addFriends( [board[j-1][i-1], board[j-1][i], board[j-1][i+1], board[j][i-1], board[j][i+1], board[j+1][i-1], board[j+1][i], board[j+1][i+1]] ); /* up */ for (auto i=1; i<x-1; i++) board[0][i].addFriends( [board[0][i-1], board[0][i+1], board[1][i-1], board[1][i], board[1][i+1]] ); /* bottom */ for (auto i=1; i<x-1; i++) board[y-1][i].addFriends( [board[y-1][i-1], board[y-1][i+1], board[y-2][i-1], board[y-2][i], board[y-2][i+1]] ); /*left*/ for (auto j=1; j<y-1; j++) board[j][0].addFriends( [board[j-1][0], board[j-1][1], board[j][1], board[j+1][0], board[j+1][1]] ); /*right*/ for (auto j=1; j<y-1; j++) board[j][x-1].addFriends( [board[j-1][x-2], board[j-1][x-1], board[j][x-2], board[j+1][x-2], board[j+1][x-1]] );
/* corners */ board[0][0].addFriends([board[0][1], board[1][0], board[1][1]] ); board[y-1][0].addFriends([board[y-2][0], board[y-2][1], board[y-1][1]] );
board[0][x-1].addFriends([board[0][x-2], board[1][x-2], board[1][x-1]] ); board[y-1][x-1].addFriends([board[y-2][x-2], board[y-2][x-1], board[y-1][x-2]] ); }
void createLandMines() { auto r = new RandomG!(Twister); for (auto i=0; i<mines; i++) { int j; do j=r.next(x*y); while (board[j/x][j%x].hasBomb); board[j/x][j%x].mineArea; } }
void updateValues() { foreach (ref row; board) foreach (ref field; row) field.updateValue; } }
public { /* flags given field * returns 1: superb * 0: continue game */ int flag(out bool changed, int y, int x) { if (y < 0 || y >= this.y || x < 0 || x >= this.x) throw new Exception("flag out of range");
if (board[y][x].isVisible) return 0;
changed = true;
board[y][x].flag; return (visibles == this.x*this.y - mines && flaggedCount <= mines) ? 1 : 0; }
/* digs given field * returns 1: superb * 0: continue game (changed indicates if board has changed) * -1: kthxbai */ int dig(out bool changed, int y, int x) { if (y < 0 || y >= this.y || x < 0 || x >= this.x) throw new MSException("dig out of range");
if (board[y][x].isVisible) return 0;
changed=true;
/*returns 0 or -1 */ auto ret = board[y][x].dig; return (visibles == this.x*this.y - mines && flaggedCount <= mines)?1:ret; }
int getFlaggedCount() { return flaggedCount; } int getVisibles() { return visibles; }
int getWidth() { return x; } int getHeight() { return y; } int getMines() { return mines; }
int opApply(int delegate(ref uint, ref RowWrapper) dg) { int ret; foreach (uint r, row; rows) if ( (ret = dg(r, row)) != 0 ) break; return ret; } }
} </lang>
Example plays
won game
Welcome! Gimme height width: :> 4 6 00 123456 1 [ ...... ] 2 [ ...... ] 3 [ ...... ] 4 [ ...... ] :> 2 2 00 123456 1 [ ...... ] 2 [ .1.... ] 3 [ ...... ] 4 [ ...... ] :> 3 3 00 123456 1 [ ...... ] 2 [ .11121 ] 3 [ 11____ ] 4 [ ______ ] :> F 2 1 01 123456 1 [ ...... ] 2 [ ?11121 ] 3 [ 11____ ] 4 [ ______ ] :> 1 3 01 123456 1 [ ..1... ] 2 [ ?11121 ] 3 [ 11____ ] 4 [ ______ ] :> 1 5 01 123456 1 [ ..1.2. ] 2 [ ?11121 ] 3 [ 11____ ] 4 [ ______ ] :> 1 1 01 123456 1 [ 1.1.2. ] 2 [ ?11121 ] 3 [ 11____ ] 4 [ ______ ] :> 1 2 KESSETOUN!!! 01 123456 1 [ 111.2. ] 2 [ ?11121 ] 3 [ 11____ ] 4 [ ______ ]
lost game
Welcome! Gimme height width: :> 4 6 00 123456 1 [ ...... ] 2 [ ...... ] 3 [ ...... ] 4 [ ...... ] :> 1 1 00 123456 1 [ ___1.. ] 2 [ ___1.. ] 3 [ _112.. ] 4 [ _1.... ] :> 1 5 BIG BADA BOOM 00 123456 1 [ ___1*. ] 2 [ ___1.. ] 3 [ _112.. ] 4 [ _1.... ]