SHOW:
|
|
- or go back to the newest paste.
| 1 | - | Copied from here: http:Q_26487697.html#a33724604 (I own the post) |
| 1 | + | /* Reproduced from my post here: http://e-e.com/Q_26487697.html#a33724604 |
| 2 | (c) 2012 RichardTheKiwi | |
| 3 | License to use freely */ | |
| 4 | ||
| 5 | -- the most basic component, the Square. | |
| 6 | if object_id('Square') is not null
| |
| 7 | drop table Square | |
| 8 | GO | |
| 9 | create table Square( | |
| 10 | row int, | |
| 11 | col int, | |
| 12 | mine bit default(0), -- this square is a mine | |
| 13 | neighbouring_mines tinyint, -- how many neighbours are mines | |
| 14 | opened bit default(0), -- 1 for opened | |
| 15 | flagged bit default(0), | |
| 16 | suspected bit default(0), | |
| 17 | primary key clustered(row,col) -- good for column travel given row | |
| 18 | ); | |
| 19 | create index ix_Square_colrow on Square(col,row); -- travel by row given column | |
| 20 | create index ix_Square_opened on Square(opened,mine); -- game ending test | |
| 21 | ||
| 22 | -- very basic procedure for showing the mine field | |
| 23 | -- @what is very flexible (and open to SQL injection, but who cares) | |
| 24 | if object_id('ShowMineField') is not null
| |
| 25 | drop proc ShowMineField | |
| 26 | GO | |
| 27 | create proc ShowMineField | |
| 28 | @what varchar(1000) | |
| 29 | as | |
| 30 | declare @sql nvarchar(max) | |
| 31 | declare @cols nvarchar(max) | |
| 32 | select @cols = coalesce(@cols+',','') + '['+convert(varchar(10),col)+']' | |
| 33 | from (select distinct col from Square) M | |
| 34 | set @sql = ' | |
| 35 | select ' + @cols + ' | |
| 36 | from (select row,col,what=' + @what + ' from Square) p | |
| 37 | pivot (max(what) for col in (' + @cols + ')) pv
| |
| 38 | order by row' | |
| 39 | --print (@sql) | |
| 40 | exec (@sql) | |
| 41 | GO | |
| 42 | ||
| 43 | -- shows the game view of the mine field | |
| 44 | if object_id('ShowMineField_Game') is not null
| |
| 45 | drop proc ShowMineField_Game | |
| 46 | GO | |
| 47 | create proc ShowMineField_Game | |
| 48 | AS | |
| 49 | exec ShowMineField 'case | |
| 50 | when opened&mine=1 then ''X'' | |
| 51 | when opened=1 then coalesce(convert(char(1),nullif(neighbouring_mines,0)),'''') | |
| 52 | when flagged=1 then ''(B)'' | |
| 53 | when suspected=1 then ''(?)'' | |
| 54 | else ''<>'' end' | |
| 55 | GO | |
| 56 | ||
| 57 | -- shows where the mines are | |
| 58 | if object_id('ShowMineField_Mines') is not null
| |
| 59 | drop proc ShowMineField_Mines | |
| 60 | GO | |
| 61 | create proc ShowMineField_Mines | |
| 62 | AS | |
| 63 | -- uncomment this line to CHEAT. or DEBUG | |
| 64 | --exec ShowMineField 'convert(char(1),mine) + ''/'' + convert(char(1),neighbouring_mines)' | |
| 65 | GO | |
| 66 | ||
| 67 | -- shows all details about all square | |
| 68 | if object_id('ShowMineField_Info') is not null
| |
| 69 | drop proc ShowMineField_Info | |
| 70 | GO | |
| 71 | create proc ShowMineField_Info | |
| 72 | AS | |
| 73 | -- uncomment this line to CHEAT. or DEBUG | |
| 74 | --exec ShowMineField ' | |
| 75 | -- convert(char(1),mine) + | |
| 76 | -- convert(char(1),neighbouring_mines) + | |
| 77 | -- convert(char(1),opened) + | |
| 78 | -- convert(char(1),flagged) + | |
| 79 | -- convert(char(1),suspected)' | |
| 80 | GO | |
| 81 | ||
| 82 | -- check to see if the game has ended | |
| 83 | if object_id('GameEnded') is not null
| |
| 84 | drop function GameEnded | |
| 85 | GO | |
| 86 | create function GameEnded() returns varchar(20) as | |
| 87 | begin | |
| 88 | if exists (select * from Square where opened&mine=1) | |
| 89 | return '** You lost' | |
| 90 | if (select count(*)-sum(1*opened)-sum(1*mine) from Square) = 0 | |
| 91 | return '** You won' | |
| 92 | return null | |
| 93 | end | |
| 94 | GO | |
| 95 | ||
| 96 | -- shows all details about all square | |
| 97 | if object_id('ShowGameStatus') is not null
| |
| 98 | drop proc ShowGameStatus | |
| 99 | GO | |
| 100 | create proc ShowGameStatus | |
| 101 | @message varchar(100) = '' | |
| 102 | AS | |
| 103 | if dbo.gameEnded()>'' set @message = dbo.gameEnded() | |
| 104 | select | |
| 105 | Opened=sum(1*opened), | |
| 106 | Flagged=sum(1*flagged), | |
| 107 | [Total Mines]=sum(1*mine), | |
| 108 | [Total Squares]=count(*), | |
| 109 | [Message]=@message | |
| 110 | from Square | |
| 111 | exec ShowMineField_Game | |
| 112 | exec ShowMineField_Mines | |
| 113 | exec ShowMineField_Info | |
| 114 | GO | |
| 115 | ||
| 116 | -- function to generate a new mine field | |
| 117 | if object_id('GenerateMineField') is not null
| |
| 118 | drop proc GenerateMineField | |
| 119 | GO | |
| 120 | create proc GenerateMineField | |
| 121 | @rows int, | |
| 122 | @cols int, | |
| 123 | @mines int | |
| 124 | AS | |
| 125 | -- clear the current field | |
| 126 | truncate table Square; | |
| 127 | -- generate entire field including mines | |
| 128 | ;with generator as ( | |
| 129 | select row=r.number, col=c.number, rn = row_number() over (order by newid()) | |
| 130 | from master..spt_values r | |
| 131 | inner join master..spt_values c on c.type='P' and c.number between 1 and @cols | |
| 132 | where r.type='P' and r.number between 1 and @rows | |
| 133 | ) | |
| 134 | insert Square (row, col, mine) | |
| 135 | select row, col, case when rn<=@mines then 1 else 0 end | |
| 136 | from generator; | |
| 137 | ||
| 138 | -- one-off counting of neighbouring mines for each mine | |
| 139 | ;with mines as ( | |
| 140 | select a.row, a.col, c=count(*) | |
| 141 | from Square a | |
| 142 | inner join Square b on abs(a.row-b.row)<=1 and abs(a.col-b.col)<=1 | |
| 143 | and not (a.col=b.col and a.row=b.row) | |
| 144 | and b.mine=1 | |
| 145 | group by a.row, a.col) | |
| 146 | update m | |
| 147 | set neighbouring_mines=coalesce(b.c,0) | |
| 148 | from Square m | |
| 149 | left join mines b on m.row=b.row and m.col=b.col | |
| 150 | ||
| 151 | -- show game board | |
| 152 | exec ShowGameStatus 'Ready'; | |
| 153 | GO | |
| 154 | ||
| 155 | -- helper function to format a reference to a cell in RC notation | |
| 156 | if object_id('formatStatus') is not null
| |
| 157 | drop function formatStatus | |
| 158 | GO | |
| 159 | create function formatStatus(@row int, @col int, @message varchar(100)) returns varchar(50) as | |
| 160 | begin | |
| 161 | return ('R' + convert(varchar(10),@row) + 'C' + convert(varchar(10),@col) + ': ' + @message)
| |
| 162 | end | |
| 163 | GO | |
| 164 | ||
| 165 | -- this toggles a cell between unmarked, safe or suspected | |
| 166 | if object_id('MarkCell') is not null
| |
| 167 | drop proc MarkCell | |
| 168 | GO | |
| 169 | create proc MarkCell @row int, @col int AS | |
| 170 | if dbo.gameEnded()>'' begin exec ShowGameStatus; return; end; | |
| 171 | ||
| 172 | update Square set | |
| 173 | flagged = case when flagged|suspected=1 then 0 else 1 end, | |
| 174 | suspected = case when flagged=1 then 1 else 0 end | |
| 175 | where row=@row and col=@col and opened=0 | |
| 176 | if @@rowcount=0 | |
| 177 | begin | |
| 178 | declare @status varchar(100) | |
| 179 | set @status = dbo.formatStatus(@row,@col,'is not valid for marking') | |
| 180 | exec ShowGameStatus @status | |
| 181 | end | |
| 182 | else | |
| 183 | exec ShowGameStatus; | |
| 184 | GO | |
| 185 | if exists (select * from sys.synonyms where name='m') | |
| 186 | drop synonym m; | |
| 187 | create synonym m for MarkCell | |
| 188 | GO | |
| 189 | ||
| 190 | -- this opens up a cells next to already opened cells, that are | |
| 191 | -- next to opened cells with 0 neighbouring mines | |
| 192 | if object_id('OpenZeroCounters') is not null
| |
| 193 | drop proc OpenZeroCounters | |
| 194 | GO | |
| 195 | create proc OpenZeroCounters AS | |
| 196 | set nocount on; | |
| 197 | select a=1 into #tmp; | |
| 198 | while @@rowcount>0 | |
| 199 | update a | |
| 200 | set opened=1, flagged=0, suspected=0 | |
| 201 | from Square a | |
| 202 | inner join Square b on abs(a.row-b.row)<=1 and abs(a.col-b.col)<=1 | |
| 203 | and b.opened=1 and b.neighbouring_mines=0 | |
| 204 | where a.opened=0 | |
| 205 | exec ShowGameStatus; | |
| 206 | GO | |
| 207 | ||
| 208 | -- this opens up a cell to reveal the big question: mine or no mine | |
| 209 | if object_id('OpenCell') is not null
| |
| 210 | drop proc OpenCell | |
| 211 | GO | |
| 212 | create proc OpenCell @row int, @col int AS | |
| 213 | if dbo.gameEnded()>'' begin exec ShowGameStatus; return; end; | |
| 214 | ||
| 215 | update Square | |
| 216 | set opened=1, flagged=0, suspected=0 | |
| 217 | where row=@row and col=@col and opened=0 | |
| 218 | if @@rowcount=0 | |
| 219 | begin | |
| 220 | declare @status varchar(100) | |
| 221 | set @status = dbo.formatStatus(@row,@col,'is not valid for opening') | |
| 222 | exec ShowGameStatus @status | |
| 223 | end | |
| 224 | else | |
| 225 | exec OpenZeroCounters | |
| 226 | GO | |
| 227 | if exists (select * from sys.synonyms where name='o') | |
| 228 | drop synonym o; | |
| 229 | create synonym o for OpenCell | |
| 230 | GO | |
| 231 | ||
| 232 | -- if there are as many neighbouring mines as flagged, | |
| 233 | -- we take a big step and open up all unflagged neighbours | |
| 234 | if object_id('QuickClear') is not null
| |
| 235 | drop proc QuickClear | |
| 236 | GO | |
| 237 | create proc QuickClear @row int, @col int AS | |
| 238 | if dbo.gameEnded()>'' begin exec ShowGameStatus; return; end; | |
| 239 | ||
| 240 | update c | |
| 241 | set opened=1 | |
| 242 | from Square a | |
| 243 | inner join Square c on abs(a.row-c.row)<=1 and abs(a.col-c.col)<=1 and c.flagged=0 | |
| 244 | where a.row=@row and a.col=@col | |
| 245 | and a.opened=1 -- only on opened cells, I think this is right(?) | |
| 246 | and a.neighbouring_mines = ( | |
| 247 | select count(*) | |
| 248 | from Square b | |
| 249 | where abs(a.row-b.row)<=1 and abs(a.col-b.col)<=1 | |
| 250 | and b.flagged=1) | |
| 251 | if @@rowcount=0 | |
| 252 | begin | |
| 253 | declare @status varchar(100) | |
| 254 | set @status = dbo.formatStatus(@row,@col,'is not valid for clearing') | |
| 255 | exec ShowGameStatus @status | |
| 256 | end | |
| 257 | else | |
| 258 | exec OpenZeroCounters | |
| 259 | GO | |
| 260 | if exists (select * from sys.synonyms where name='c') | |
| 261 | drop synonym c; | |
| 262 | create synonym c for QuickClear | |
| 263 | GO | |
| 264 | ||
| 265 | exec GenerateMineField 50,50,10; -- this and open a single took 21s to clear | |
| 266 | exec m 10, 5; | |
| 267 | exec o 10, 1; | |
| 268 | exec c 9, 4; |