在Delphi中,我有一个数组形式的Connect 4板表示(7列x 6行):
TBoard = Array[1..7, 1..6] of SmallInt;
Board: TBoard; // instance ob TBoard
每个元素可以有三种不同的状态:
现在我需要一个功能来检查是否有赢家或平局:
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;
您如何看待这种方法?你有更好的(更快/更短)的解决方案吗?
非常感谢!
答案 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;
您不需要begin
和end
部分:
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;
或者,您可以使用label
和goto
一次中断两个循环。
<强>更新强>
我现在看到你可以做到
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;
}
您必须为执行最后一步的玩家的位板调用该功能