📄 servmain.pas
字号:
unit ServMain;
interface
uses
ActiveX, MtsObj, Mtx, ComObj, TTTServer_TLB;
type
PGameData = ^TGameData;
TGameData = array[1..3, 1..3] of Byte;
TGameServer = class(TMtsAutoObject, IGameServer)
private
procedure CalcComputerMove(GameData: PGameData; Skill: SkillLevels;
var X, Y: Integer);
function CalcGameStatus(GameData: PGameData): GameResults;
function GetSharedPropertyGroup: ISharedPropertyGroup;
procedure CheckCallerSecurity;
protected
procedure NewGame(out GameID: Integer); safecall;
procedure ComputerMove(GameID: Integer; SkillLevel: SkillLevels; out X,
Y: Integer; out GameRez: GameResults); safecall;
procedure PlayerMove(GameID, X, Y: Integer; out GameRez: GameResults);
safecall;
end;
implementation
uses ComServ, Windows, SysUtils;
const
GameDataStr = 'TTTGameData%d';
EmptySpot = 0;
PlayerSpot = $1;
ComputerSpot = $2;
function TGameServer.GetSharedPropertyGroup: ISharedPropertyGroup;
var
SPGMgr: ISharedPropertyGroupManager;
LockMode, RelMode: Integer;
Exists: WordBool;
begin
if ObjectContext = nil then
raise Exception.Create('Failed to obtain object context');
// Create shared property group for this object
OleCheck(ObjectContext.CreateInstance(CLASS_SharedPropertyGroupManager,
ISharedPropertyGroupManager, SPGMgr));
LockMode := LockSetGet;
RelMode := Process;
Result := SPGMgr.CreatePropertyGroup('DelphiTTT', LockMode, RelMode, Exists);
if Result = nil then
raise Exception.Create('Failed to obtain property group');
end;
procedure TGameServer.NewGame(out GameID: Integer);
var
SPG: ISharedPropertyGroup;
SProp: ISharedProperty;
Exists: WordBool;
GameData: OleVariant;
begin
// Use caller's role to validate security
CheckCallerSecurity;
// Get shared property group for this object
SPG := GetSharedPropertyGroup;
// Create or retrieve NextGameID shared property
SProp := SPG.CreateProperty('NextGameID', Exists);
if Exists then GameID := SProp.Value
else GameID := 0;
// Increment and store NextGameID shared property
SProp.Value := GameID + 1;
// Create game data array
GameData := VarArrayCreate([1, 3, 1, 3], varByte);
SProp := SPG.CreateProperty(Format(GameDataStr, [GameID]), Exists);
SProp.Value := GameData;
SetComplete;
end;
procedure TGameServer.ComputerMove(GameID: Integer;
SkillLevel: SkillLevels; out X, Y: Integer; out GameRez: GameResults);
var
Exists: WordBool;
PropVal: OleVariant;
GameData: PGameData;
SProp: ISharedProperty;
begin
// Get game data shared property
SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]),
Exists);
// Get game data array and lock it for more efficient access
PropVal := SProp.Value;
GameData := PGameData(VarArrayLock(PropVal));
try
// If game isn't over, then let computer make a move
GameRez := CalcGameStatus(GameData);
if GameRez = grInProgress then
begin
CalcComputerMove(GameData, SkillLevel, X, Y);
// Save away new game data array
SProp.Value := PropVal;
// Check for end of game
GameRez := CalcGameStatus(GameData);
end;
finally
VarArrayUnlock(PropVal);
end;
SetComplete;
end;
procedure TGameServer.PlayerMove(GameID, X, Y: Integer;
out GameRez: GameResults);
var
Exists: WordBool;
PropVal: OleVariant;
GameData: PGameData;
SProp: ISharedProperty;
begin
// Get game data shared property
SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]),
Exists);
// Get game data array and lock it for more efficient access
PropVal := SProp.Value;
GameData := PGameData(VarArrayLock(PropVal));
try
// Make sure game isn't over
GameRez := CalcGameStatus(GameData);
if GameRez = grInProgress then
begin
// If spot isn't empty, raise exception
if GameData[X, Y] <> EmptySpot then
raise Exception.Create('Spot is occupied!');
// Allow move
GameData[X, Y] := PlayerSpot;
// Save away new game data array
SProp.Value := PropVal;
// Check for end of game
GameRez := CalcGameStatus(GameData);
end;
finally
VarArrayUnlock(PropVal);
end;
SetComplete;
end;
function TGameServer.CalcGameStatus(GameData: PGameData): GameResults;
var
I, J: Integer;
begin
// First check for a winner
if GameData[1, 1] <> EmptySpot then
begin
// Check top row, left column, and top left to bottom right diagonal for win
if ((GameData[1, 1] = GameData[1, 2]) and (GameData[1, 1] = GameData[1, 3])) or
((GameData[1, 1] = GameData[2, 1]) and (GameData[1, 1] = GameData[3, 1])) or
((GameData[1, 1] = GameData[2, 2]) and (GameData[1, 1] = GameData[3, 3])) then
begin
Result := GameData[1, 1] + 1; // Game result is spot ID + 1
Exit;
end;
end;
if GameData[3, 3] <> EmptySpot then
begin
// Check bottom row and right column for win
if ((GameData[3, 3] = GameData[3, 2]) and (GameData[3, 3] = GameData[3, 1])) or
((GameData[3, 3] = GameData[2, 3]) and (GameData[3, 3] = GameData[1, 3])) then
begin
Result := GameData[3, 3] + 1; // Game result is spot ID + 1
Exit;
end;
end;
if GameData[2, 2] <> EmptySpot then
begin
// Check middle row, middle column, and bottom left to top right diagonal for win
if ((GameData[2, 2] = GameData[2, 1]) and (GameData[2, 2] = GameData[2, 3])) or
((GameData[2, 2] = GameData[1, 2]) and (GameData[2, 2] = GameData[3, 2])) or
((GameData[2, 2] = GameData[3, 1]) and (GameData[2, 2] = GameData[1, 3])) then
begin
Result := GameData[2, 2] + 1; // Game result is spot ID + 1
Exit;
end;
end;
// Finally, check for game still in progress
for I := 1 to 3 do
for J := 1 to 3 do
if GameData[I, J] = 0 then
begin
Result := grInProgress;
Exit;
end;
// If we get here, then we've tied
Result := grTie;
end;
procedure TGameServer.CalcComputerMove(GameData: PGameData;
Skill: SkillLevels; var X, Y: Integer);
type
// Used to scan for possible moves by either row, column, or diagonal line
TCalcType = (ctRow, ctColumn, ctDiagonal);
// mtWin = one move away from win, mtBlock = opponent is one move away from
// win, mtOne = I occupy one other spot in this line, mtNew = I occupy no
// spots on this line
TMoveType = (mtWin, mtBlock, mtOne, mtNew);
var
CurrentMoveType: TMoveType;
function DoCalcMove(CalcType: TCalcType; Position: Integer): Boolean;
var
RowData, I, J, CheckTotal: Integer;
PosVal, Mask: Byte;
begin
Result := False;
RowData := 0;
X := 0;
Y := 0;
if CalcType = ctRow then
begin
I := Position;
J := 1;
end
else if CalcType = ctColumn then
begin
I := 1;
J := Position;
end
else begin
I := 1;
case Position of
1: J := 1; // scanning from top left to bottom right
2: J := 3; // scanning from top right to bottom left
else
Exit; // bail; only 2 diagonal scans
end;
end;
// Mask masks off Player or Computer bit, depending on whether we're thinking
// offensively or defensively. Checktotal determines whether that is a row
// we need to move into.
case CurrentMoveType of
mtWin:
begin
Mask := PlayerSpot;
CheckTotal := 4;
end;
mtNew:
begin
Mask := PlayerSpot;
CheckTotal := 0;
end;
mtBlock:
begin
Mask := ComputerSpot;
CheckTotal := 2;
end;
else
begin
Mask := 0;
CheckTotal := 2;
end;
end;
// loop through all lines in current CalcType
repeat
// Get status of current spot (X, O, or empty)
PosVal := GameData[I, J];
// Save away last empty spot in case we decide to move here
if PosVal = 0 then
begin
X := I;
Y := J;
end
else
// If spot isn't empty, then add masked value to RowData
Inc(RowData, (PosVal and not Mask));
if (CalcType = ctDiagonal) and (Position = 2) then
begin
Inc(I);
Dec(J);
end
else begin
if CalcType in [ctRow, ctDiagonal] then Inc(J);
if CalcType in [ctColumn, ctDiagonal] then Inc(I);
end;
until (I > 3) or (J > 3);
// If RowData adds up, then we must block or win, depending on whether we're
// thinking offensively or defensively.
Result := (X <> 0) and (RowData = CheckTotal);
if Result then
begin
GameData[X, Y] := ComputerSpot;
Exit;
end;
end;
var
A, B, C: Integer;
begin
if Skill = slAwake then
begin
// First look to win the game, next look to block a win
for A := Ord(mtWin) to Ord(mtBlock) do
begin
CurrentMoveType := TMoveType(A);
for B := Ord(ctRow) to Ord(ctDiagonal) do
for C := 1 to 3 do
if DoCalcMove(TCalcType(B), C) then Exit;
end;
// Next look to take the center of the board
if GameData[2, 2] = 0 then
begin
GameData[2, 2] := ComputerSpot;
X := 2;
Y := 2;
Exit;
end;
// Next look for the most advantageous position on a line
for A := Ord(mtOne) to Ord(mtNew) do
begin
CurrentMoveType := TMoveType(A);
for B := Ord(ctRow) to Ord(ctDiagonal) do
for C := 1 to 3 do
if DoCalcMove(TCalcType(B), C) then Exit;
end;
end;
// Finally (or if skill level is unconscious), just find the first open place
for A := 1 to 3 do
for B := 1 to 3 do
if GameData[A, B] = 0 then
begin
GameData[A, B] := ComputerSpot;
X := A;
Y := B;
Exit;
end;
end;
procedure TGameServer.CheckCallerSecurity;
begin
// Just for fun, only allow those in the "TTT" role to play the game.
if IsSecurityEnabled and not IsCallerInRole('TTT') then
raise Exception.Create('Only those in the TTT role can play tic-tac-toe');
end;
initialization
TAutoObjectFactory.Create(ComServer, TGameServer, Class_GameServer,
ciMultiInstance, tmApartment);
end.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -