Minesweeper game/D: Difference between revisions

Content added Content deleted
No edit summary
Line 1: Line 1:
Tango based implementation
Tango and Phobos based implementations


== Main module ==
== Tango ==
=== Main module ===
<lang D>import tango.io.Stdout;
<lang D>import tango.io.Stdout;
import tango.io.Console;
import tango.io.Console;
Line 87: Line 88:
</lang>
</lang>


== Example plays ==
=== Example plays ===


=== won game ===
==== won game ====
Welcome!
Welcome!
Gimme height width:
Gimme height width:
Line 142: Line 143:
4 [ ______ ]
4 [ ______ ]


=== lost game ===
==== lost game ====
Welcome!
Welcome!
Gimme height width:
Gimme height width:
Line 165: Line 166:
4 [ _1.... ]
4 [ _1.... ]


== MineSweeper module ==
=== MineSweeper module ===
<lang D>module MineSweeper;
<lang D>module MineSweeper;


Line 400: Line 401:
return ret;
return ret;
}
}
}
}
</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;

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>

=== Example plays ===

==== won game ====
Welcome!
Gimme height width:
:> 4 6
00 123456
1 [ ...... ]
2 [ ...... ]
3 [ ...... ]
4 [ ...... ]
:> P 1 1
00 123456
1 [ ]
2 [ 112121 ]
3 [ ...... ]
4 [ ...... ]
:> F 3 2
:> F 3 4
:> F 3 6
:> 3 1
:> C 2 4
:> P
03 123456
1 [ ]
2 [ 112121 ]
3 [ 1?2?3? ]
4 [ ...... ]
:> C 3 3
:> 4 1
:> P
03 123456
1 [ ]
2 [ 112121 ]
3 [ 1?2?3? ]
4 [ 1122.. ]
:> 4 6
DEAR DIARY... JACKPOT!
03 123456
1 [ ]
2 [ 112121 ]
3 [ 1?2?3? ]
4 [ 1122.2 ]

==== lost game ====
Welcome!
Gimme height width:
:> 4 6
00 123456
1 [ ...... ]
2 [ ...... ]
3 [ ...... ]
4 [ ...... ]
:> P 1 1
00 123456
1 [ 1..... ]
2 [ ...... ]
3 [ ...... ]
4 [ ...... ]
:> P 1 2
00 123456
1 [ 11.... ]
2 [ ...... ]
3 [ ...... ]
4 [ ...... ]
:> 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;
}
}
}
}