/* Reproduced from my post here: http://e-e.com/Q_26487697.html#a33724604 (c) 2012 RichardTheKiwi License to use freely */ -- the most basic component, the Square. if object_id('Square') is not null drop table Square GO create table Square( row int, col int, mine bit default(0), -- this square is a mine neighbouring_mines tinyint, -- how many neighbours are mines opened bit default(0), -- 1 for opened flagged bit default(0), suspected bit default(0), primary key clustered(row,col) -- good for column travel given row ); create index ix_Square_colrow on Square(col,row); -- travel by row given column create index ix_Square_opened on Square(opened,mine); -- game ending test -- very basic procedure for showing the mine field -- @what is very flexible (and open to SQL injection, but who cares) if object_id('ShowMineField') is not null drop proc ShowMineField GO create proc ShowMineField @what varchar(1000) as declare @sql nvarchar(max) declare @cols nvarchar(max) select @cols = coalesce(@cols+',','') + '['+convert(varchar(10),col)+']' from (select distinct col from Square) M set @sql = ' select ' + @cols + ' from (select row,col,what=' + @what + ' from Square) p pivot (max(what) for col in (' + @cols + ')) pv order by row' --print (@sql) exec (@sql) GO -- shows the game view of the mine field if object_id('ShowMineField_Game') is not null drop proc ShowMineField_Game GO create proc ShowMineField_Game AS exec ShowMineField 'case when opened&mine=1 then ''X'' when opened=1 then coalesce(convert(char(1),nullif(neighbouring_mines,0)),'''') when flagged=1 then ''(B)'' when suspected=1 then ''(?)'' else ''<>'' end' GO -- shows where the mines are if object_id('ShowMineField_Mines') is not null drop proc ShowMineField_Mines GO create proc ShowMineField_Mines AS -- uncomment this line to CHEAT. or DEBUG --exec ShowMineField 'convert(char(1),mine) + ''/'' + convert(char(1),neighbouring_mines)' GO -- shows all details about all square if object_id('ShowMineField_Info') is not null drop proc ShowMineField_Info GO create proc ShowMineField_Info AS -- uncomment this line to CHEAT. or DEBUG --exec ShowMineField ' -- convert(char(1),mine) + -- convert(char(1),neighbouring_mines) + -- convert(char(1),opened) + -- convert(char(1),flagged) + -- convert(char(1),suspected)' GO -- check to see if the game has ended if object_id('GameEnded') is not null drop function GameEnded GO create function GameEnded() returns varchar(20) as begin if exists (select * from Square where opened&mine=1) return '** You lost' if (select count(*)-sum(1*opened)-sum(1*mine) from Square) = 0 return '** You won' return null end GO -- shows all details about all square if object_id('ShowGameStatus') is not null drop proc ShowGameStatus GO create proc ShowGameStatus @message varchar(100) = '' AS if dbo.gameEnded()>'' set @message = dbo.gameEnded() select Opened=sum(1*opened), Flagged=sum(1*flagged), [Total Mines]=sum(1*mine), [Total Squares]=count(*), [Message]=@message from Square exec ShowMineField_Game exec ShowMineField_Mines exec ShowMineField_Info GO -- function to generate a new mine field if object_id('GenerateMineField') is not null drop proc GenerateMineField GO create proc GenerateMineField @rows int, @cols int, @mines int AS -- clear the current field truncate table Square; -- generate entire field including mines ;with generator as ( select row=r.number, col=c.number, rn = row_number() over (order by newid()) from master..spt_values r inner join master..spt_values c on c.type='P' and c.number between 1 and @cols where r.type='P' and r.number between 1 and @rows ) insert Square (row, col, mine) select row, col, case when rn<=@mines then 1 else 0 end from generator; -- one-off counting of neighbouring mines for each mine ;with mines as ( select a.row, a.col, c=count(*) from Square a inner join Square b on abs(a.row-b.row)<=1 and abs(a.col-b.col)<=1 and not (a.col=b.col and a.row=b.row) and b.mine=1 group by a.row, a.col) update m set neighbouring_mines=coalesce(b.c,0) from Square m left join mines b on m.row=b.row and m.col=b.col -- show game board exec ShowGameStatus 'Ready'; GO -- helper function to format a reference to a cell in RC notation if object_id('formatStatus') is not null drop function formatStatus GO create function formatStatus(@row int, @col int, @message varchar(100)) returns varchar(50) as begin return ('R' + convert(varchar(10),@row) + 'C' + convert(varchar(10),@col) + ': ' + @message) end GO -- this toggles a cell between unmarked, safe or suspected if object_id('MarkCell') is not null drop proc MarkCell GO create proc MarkCell @row int, @col int AS if dbo.gameEnded()>'' begin exec ShowGameStatus; return; end; update Square set flagged = case when flagged|suspected=1 then 0 else 1 end, suspected = case when flagged=1 then 1 else 0 end where row=@row and col=@col and opened=0 if @@rowcount=0 begin declare @status varchar(100) set @status = dbo.formatStatus(@row,@col,'is not valid for marking') exec ShowGameStatus @status end else exec ShowGameStatus; GO if exists (select * from sys.synonyms where name='m') drop synonym m; create synonym m for MarkCell GO -- this opens up a cells next to already opened cells, that are -- next to opened cells with 0 neighbouring mines if object_id('OpenZeroCounters') is not null drop proc OpenZeroCounters GO create proc OpenZeroCounters AS set nocount on; select a=1 into #tmp; while @@rowcount>0 update a set opened=1, flagged=0, suspected=0 from Square a inner join Square b on abs(a.row-b.row)<=1 and abs(a.col-b.col)<=1 and b.opened=1 and b.neighbouring_mines=0 where a.opened=0 exec ShowGameStatus; GO -- this opens up a cell to reveal the big question: mine or no mine if object_id('OpenCell') is not null drop proc OpenCell GO create proc OpenCell @row int, @col int AS if dbo.gameEnded()>'' begin exec ShowGameStatus; return; end; update Square set opened=1, flagged=0, suspected=0 where row=@row and col=@col and opened=0 if @@rowcount=0 begin declare @status varchar(100) set @status = dbo.formatStatus(@row,@col,'is not valid for opening') exec ShowGameStatus @status end else exec OpenZeroCounters GO if exists (select * from sys.synonyms where name='o') drop synonym o; create synonym o for OpenCell GO -- if there are as many neighbouring mines as flagged, -- we take a big step and open up all unflagged neighbours if object_id('QuickClear') is not null drop proc QuickClear GO create proc QuickClear @row int, @col int AS if dbo.gameEnded()>'' begin exec ShowGameStatus; return; end; update c set opened=1 from Square a inner join Square c on abs(a.row-c.row)<=1 and abs(a.col-c.col)<=1 and c.flagged=0 where a.row=@row and a.col=@col and a.opened=1 -- only on opened cells, I think this is right(?) and a.neighbouring_mines = ( select count(*) from Square b where abs(a.row-b.row)<=1 and abs(a.col-b.col)<=1 and b.flagged=1) if @@rowcount=0 begin declare @status varchar(100) set @status = dbo.formatStatus(@row,@col,'is not valid for clearing') exec ShowGameStatus @status end else exec OpenZeroCounters GO if exists (select * from sys.synonyms where name='c') drop synonym c; create synonym c for QuickClear GO exec GenerateMineField 50,50,10; -- this and open a single took 21s to clear exec m 10, 5; exec o 10, 1; exec c 9, 4;