连接4:检查获胜者

时间:2011-02-24 17:58:28

标签: delphi pascal

在Delphi中,我有一个数组形式的Connect 4板表示(7列x 6行):

TBoard = Array[1..7, 1..6] of SmallInt;
Board: TBoard; // instance ob TBoard

每个元素可以有三种不同的状态:

  • 1 =玩家1的棋子
  • 0 =空
  • -1 =玩家2的棋子

现在我需要一个功能来检查是否有赢家或平局:

function CheckForWinner(): SmallInt;

...其中1是玩家1的胜利,0是平局,-1是玩家2的胜利,“nil”是指尚未结束的游戏。

我的草稿如下 - 分成两个单一的功能:

function CheckForWinner(): SmallInt;
var playerToCheck: ShortInt;
    s, z: Byte;
    draw: Boolean;
begin
  draw := TRUE;
  for s := 1 to 7 do begin
    for z := 1 to 6 do begin
      if Board[s, z] = 0 then draw := FALSE; // if there are empty fields then it is no draw
    end;
  end;
  if draw then begin
    result := 0;
  end
  else begin
    playerToCheck := Board[lastPieceX, lastPieceY]; // only for last-moving player
    if searchRow(playerToCheck, +1, 0, lastPieceX, lastPieceY) then // search right/left
      result := playerToCheck
    else if searchRow(playerToCheck, 0, +1, lastPieceX, lastPieceY) then // search up/down
      result := playerToCheck
    else if searchRow(playerToCheck, +1, +1, lastPieceX, lastPieceY) then // search right-down/left-up
      result := playerToCheck
    else if searchRow(playerToCheck, +1, -1, lastPieceX, lastPieceY) then // search right-up/left-down
      result := playerToCheck;
    else
      result := nil;
    end;
  end;
end;

function searchRow(player: SmallInt; sChange, zChange: ShortInt; startS, startZ: Byte): Boolean;
var inRow, s, z: SmallInt;
begin
  inRow := 0;
  s := startS;
  z := startZ;
  while (Board[s, z] = player) AND (inRow < 4) AND (s >= 1) AND (s <= 7) AND (z >= 1) AND (z <= 6) do begin
    s := s+sChange;
    z := z+zChange;
    inRow := inRow+1;
  end;
  s := startS-sChange;
  z := startZ-zChange;
  while (Board[s, z] = player) AND (inRow < 4) AND (s >= 1) AND (s <= 7) AND (z >= 1) AND (z <= 6) do begin
    s := s-sChange;
    z := z-zChange;
    inRow := inRow+1;
  end;
  if inRow = 4 then
    result := TRUE
  else
    result := FALSE;
end;

您如何看待这种方法?你有更好的(更快/更短)的解决方案吗?

非常感谢!

4 个答案:

答案 0 :(得分:4)

我没看过你的代码。我只是选择用一张空白的名字写一些自己。

这是我的版本:

const
  RowCount = 6;
  ColCount = 7;

type
  TState = (stNone, stA, stB);
  TBoard = array [1..RowCount] of array [1..ColCount] of TState;

function ValidLocation(Row, Col: Integer): Boolean;
begin
  Result := InRange(Row, 1, RowCount) and InRange(Col, 1, ColCount);
end;

procedure Check(
  const Board: TBoard;
  const StartRow, StartCol: Integer;
  const RowDelta, ColDelta: Integer;
  out Winner: TState
);
var
  Row, Col, Count: Integer;
  State: TState;
begin
  Winner := stNone;
  Row := StartRow;
  Col := StartCol;
  State := Board[Row, Col];
  if State=stNone then
    exit;
  Count := 0;
  while ValidLocation(Row, Col) and (Board[Row, Col]=State) do begin
    inc(Count);
    if Count=4 then begin
      Winner := State;
      exit;
    end;
    inc(Row, RowDelta);
    inc(Col, ColDelta);
  end;
end;

function Winner(const Board: TBoard): TState;
var
  Row, Col: Integer;
begin
  for Row := 1 to RowCount do begin
    for Col := 1 to ColCount do begin
      Check(Board, Row, Col, 0, 1, Result);//check row
      if Result<>stNone then
        exit;
      Check(Board, Row, Col, 1, 0, Result);//check column
      if Result<>stNone then 
        exit;
      Check(Board, Row, Col, 1, 1, Result);//check diagonal
      if Result<>stNone then 
        exit;
      Check(Board, Row, Col, 1, -1, Result);//check other diagonal
      if Result<>stNone then 
        exit;
    end;
  end;
  Result := stNone;
end;

很长的一堆代码。使用蛮力方法,而不是性能对Connect 4很重要。不喜欢四条相同的if Result<>stNone then exit;行,但你肯定会想到一种更清洁的方式。代码尚未运行。它可能甚至不起作用!!正是我的大脑试图解决问题的方式。

答案 1 :(得分:3)

以与您相同的方式检查获胜者,只需少一点代码。 我想你不需要检查所有字段来确定游戏是否完成。当你在游戏中放下一块时,只需增加一个计数器。如果计数器达到42并且还没有获胜者,则该游戏是平局。

function CheckRow(x, y, xd, yd: Integer): Boolean;
var
  c: Integer;

  function RowLength(x, y, xd, yd: Integer): Integer;
  begin
    Result := 0;
    repeat
      Inc(Result);
      Inc(x, xd);
      Inc(y, yd);
    until not ((x in [1..7]) and (y in [1..6]) and (Board[x, y] = c));
  end;

begin
  c := Board[x, y];

  Result := 4 <= RowLength(x, y, xd, yd) + RowLength(x, y, xd*-1, yd*-1) - 1;
end;

function CheckForWinner(x, y: Integer): Integer;
begin
  Result := 0;
  if CheckRow(x, y, 0, 1) or CheckRow(x, y, 1, 1) or
     CheckRow(x, y, 1, 0) or CheckRow(x, y, 1, -1) then
    Result := Board[x,y];
end;

答案 2 :(得分:2)

免责声明:我没有详细研究过算法。以下评论仅仅是我在盯着代码不到十秒后的第一反应。

我有一些非常快速的评论。首先,我想

TCellState = (csUnoccupied, csPlayerA, csPlayerB)
TBoard = Array[1..7, 1..6] of TCellState;

更好。当然,您可以通过

保存与旧代码的兼容性
TCellState = (csUnoccupied = 0, csPlayerA = 1, csPlayerB = -1)

其次,

draw := true;
for s := 1 to 7 do begin
  for z := 1 to 6 do begin
    if Board[s, z] = 0 then draw := false;
  end;
end;

您不需要beginend部分:

draw := TRUE;
for s := 1 to 7 do
  for z := 1 to 6 do
    if Board[s, z] = 0 then
      draw := false;

更重要的是,作为性能的提升,您应该在将drawn设置为false后立即中断循环:

draw := true;
for s := 1 to 7 do
  for z := 1 to 6 do
    if Board[s, z] = 0 then
    begin
      draw := false;
      break;
    end;

但是,这只会打破z循环。要打破这两个循环,最好的方法是将整个块放在本地函数中。我们称之为CheckDraw

function CheckDraw: boolean;
begin
  result := true;
  for s := 1 to 7 do
    for z := 1 to 6 do
      if Board[s, z] = 0 then
        Exit(false);
end;

或者,您可以使用labelgoto一次中断两个循环。

<强>更新

我现在看到你可以做到

for s := 1 to 7 do
  for z := 1 to 6 do
    if Board[s, z] = 0 then
      Exit(0);

,您甚至不需要引入draw局部变量!

结束更新

此外,

if inRow = 4 then
   result := TRUE
 else
   result := FALSE;

很糟糕。你应该做的只是

result := inRow = 4;

最后,按照我的口味

s := s+sChange;

应该写

inc(s, sChange);

inRow := inRow+1

应该是

inc(inRow);

哦,nil是指针,不是整数。

答案 3 :(得分:1)

来自John Tromp的Fhourstones Benchmark的源代码使用了一种迷人的算法来测试连接四场比赛的胜利。该算法使用以下游戏的位板表示:

.  .  .  .  .  .  .  TOP
5 12 19 26 33 40 47
4 11 18 25 32 39 46
3 10 17 24 31 38 45
2  9 16 23 30 37 44
1  8 15 22 29 36 43
0  7 14 21 28 35 42  BOTTOM

红色播放器有一个位板,黄色播放器有一个位板。 0表示空单元,1表示填充单元。位板存储在无符号的64位整数变量中。比特6,13,20,27,34,41,> = 48必须是0。

算法是:

// return whether 'board' includes a win
bool haswon(unsigned __int64 board)
{
    unsigned __int64 y = board & (board >> 6);
    if (y & (y >> 2 * 6))     // check \ diagonal
        return true;
    y = board & (board >> 7);
    if (y & (y >> 2 * 7))     // check horizontal
        return true;
    y = board & (board >> 8);
    if (y & (y >> 2 * 8))     // check / diagonal
        return true;
    y = board & (board >> 1);
    if (y & (y >> 2))         // check vertical
        return true;
    return false;
}

您必须为执行最后一步的玩家的位板调用该功能