Minesweeper game/D
Tango and Phobos based implementations
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>
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
<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>
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/questions/5372646/. 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();
//Uniform function call syntax. 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.string; import std.algorithm; import std.range;
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) => acc ~ to!string(column + 1), accLine = (string acc, uint row) { auto accCell = (string acc, uint column) => 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>