65.9K
CodeProject 正在变化。 阅读更多。
Home

SQL 战舰 2008

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.20/5 (5投票s)

2007年12月15日

CPOL

4分钟阅读

viewsIcon

49520

downloadIcon

293

使用 GEOMETRY 数据类型和 SQL Server 中的空间函数实现的单人游戏。

引言

Microsoft SQL Server 2008 引入了两个新的“空间”数据类型:GEOGRAPHYGEOMETRY。它们可用于存储、检索和查询二维数据,包括纬度/经度坐标(使用 GEOGRAPHY 数据类型)和任何平面坐标空间(使用 GEOMETRY)。

战舰的规则在维基百科上 - 去那里阅读,完成后本文仍然在这里...

...欢迎回来。正如您所见,战舰通常在一个 10x10 的网格上进行(例如右侧的示例)- 这正是 GEOMETRY 数据类型可以用于的。如果您要在过程式语言中构建此游戏,您可能会从二维数组或类似的数据结构开始。

在 SQL Server 中,我们根本不会“创建”特定的数据结构。相反,我们将仅创建表示船只的 LINE(线段)并为每次射击创建 POINT(点),然后检查它们是否相交(即命中)或不相交(即未命中)。为简单起见,这只是一个单人游戏版本 - 船只自动放置,您只需在最少的射击次数内将其击沉

模式

数据库模式非常简单:两个 GEOMETRY 列用绿色突出显示。我还使用了 DATETIME 列 - 并非必需 - 只是因为它们也是 SQL Server 2008 的新功能。

您还会注意到 PlayOrder 列是“隐藏”的 - 将来,代码可以扩展以允许两人“竞争”(创建自己的棋盘,并在独立的 SQL 连接上射击别人的棋盘)- 但这留给读者作为练习...

关于“代码”

代码实际上包括表定义、静态查找数据以及少量存储过程。

NewGame 需要您的姓名来创建 Player 和 Game 记录,然后生成新的/随机的船只棋盘。它返回的 GameIdPlayerId 是玩游戏所必需的。它使用了另一个存储过程 - AddShip - 您也可以直接使用它来创建游戏棋盘。
Shoot 您与游戏互动的主要方式 - 使用提供的 GameIdPlayerId 执行它来在棋盘上“射击”,然后查看您是否击中了船只或未击中。
Cheat 显示棋盘(您正在尝试“猜测”的位置)。这对于测试和理解代码很有用,但如果您正在玩游戏,请不要诱惑!
Reset 清除您的射击,以便您可以从头开始玩同一个棋盘。

“Well Known Text”目前不“为人所知”。

我以前从未听说过 Well Known Text (WKT) - 但同样,维基百科有一个有用的定义。目前您只需要了解两种 WKT 类型(以及如何在 T-SQL 中使用它)。

LINESTRING

DECLARE @line  VARCHAR(500);
DECLARE @lingG GEOMETRY
-- LINESTRING(1 1,4 5)    
SET @line  = 'LINESTRING('+CAST(@StartX AS VARCHAR(2))+' '+CAST(@StartY AS VARCHAR(2))+
             ','+CAST(@EndX AS VARCHAR(2))+' '+CAST(@EndY AS VARCHAR(2))+')'
SET @lineG = geometry::STGeomFromText(@line, 4326))

POINT

DECLARE @point  VARCHAR(500);
DECLARE @pointG GEOMETRY
-- POINT(2 2)    
SET @point  = 'POINT('+CAST(@X AS VARCHAR(2))+' '+CAST(@Y AS VARCHAR(2))')'
SET @pointG = geometry::STGeomFromText(@point, 4326)) 

STIntersects

虽然有数十种 GEOMETRY 特定的函数(STLengthSTArea等等),但对于“命中测试”,我们实际上只需要 STIntersects() - 无论是在随机生成船只时(以确保它们不共享位置)

-- Loop through ships (biggest to smallest) placing them
-- where they don't intersect with other ships
DECLARE ShipCursor CURSOR FOR
SELECT Id, Length FROM LU_Ship ORDER BY Length DESC
OPEN ShipCursor 
DECLARE @ShipId TINYINT; DECLARE @ShipLength TINYINT
FETCH NEXT FROM ShipCursor INTO @ShipId, @ShipLength
WHILE (@@FETCH_STATUS <> -1)
BEGIN
    DECLARE @placed BIT; SET @placed = 0
    WHILE (@placed = 0)
    BEGIN
        PRINT '--- Attempting to place ship '+CAST (@ShipId AS VARCHAR(2))
        SET @Orientation = ROUND(RAND(),0) -- Choose north/south or east/west
        IF @Orientation = 0
        BEGIN -- 'Horizontal'
            SET @StartX = ROUND(RAND()*(@BoardSize-1-@ShipLength)+1,0)
            SET @StartY = ROUND(RAND()*(@BoardSize-1)+1,0)
            SET @EndX = @StartX + @ShipLength - 1
            SET @EndY = @StartY
        END
        ELSE
        BEGIN -- 'Vertical'
            SET @StartX = ROUND(RAND()*(@BoardSize-1)+1,0)
            SET @StartY = ROUND(RAND()*(@BoardSize-1-@ShipLength)+1,0)
            SET @EndX = @StartX 
            SET @EndY = @StartY + @ShipLength - 1
        END
        -- Attempt to 'save' ship to board: STIntersects is called in AddShip
        EXEC AddShip @GameId, @PlayerId, @ShipId, @StartX, @StartY, @EndX, @EndY    
        -- Check the result, if it wasn't saved it must have overlapped, 
        -- so we'll try again in the WHILE loop
        SELECT @placed  = COUNT(*)
        FROM   GamePlayerShip
        WHERE  GameId   = @GameId AND PlayerId = @PlayerId AND ShipId   = @ShipId
    END -- WHILE
    SET @placed = 0
FETCH NEXT FROM ShipCursor INTO @ShipId, @ShipLength
END
CLOSE ShipCursor; DEALLOCATE ShipCursor

还是在游戏本身进行时。

-- Hit test: does the point intersect a ship 'line'?
SELECT     @GamePlayerShipId = Id
     ,  @ShipId = ShipId
FROM    GamePlayerShip
WHERE   [Line].STIntersects(@point) = 1 -- Means point intersects line
AND     GameId = @GameId
AND        PlayerId = @PlayerId

安装“代码”

只需在 SQL Server 2008 中创建一个新数据库 - 我将其命名为 **Battleship**,然后执行提供的 SQL 脚本 01_Battleship.sql02_Looksup.sql03_Diagram.sql。它(应该)就是这么简单,生成的对象应该看起来像这样。

运行“代码”

这是一个示例“游戏”(好的,您可以看到我做的第一件事是作弊以找出射击位置)。

请注意,我“同时”运行了所有这些查询来获取屏幕截图。您通常会一次执行一个 Shoot

游戏的思路是您继续 Shoot - 收到 Hit(命中)、Miss(未命中)或 Sunk(击沉)的结果 - 直到您击沉所有五艘船,此时代码将返回 Won(获胜)。

结论

T-SQL 代码(不包括表定义)字面意义上只有大约 100 行,所以没有太多可讨论的了。

可能的扩展

  • StDistance() 可用于随机棋盘生成,以确保船只彼此之间至少有一定数量的格子距离(如果您愿意,还可以用“未命中,但很近!”来响应 Shoot)。
  • 制作一个双人游戏,但添加某种“状态”(游戏正在构建、棋盘已提交、轮到谁了),以便两名 DBA 可以从不同的 Management Studio 实例进行游戏。
  • 允许“计算机”成为第二个玩家,随机射击直到命中,然后使用空间函数“决定”后续射击的最佳猜测。
  • 可配置的棋盘大小、船只、射击规则。

更多阅读

© . All rights reserved.