1 | unit UGame;
|
---|
2 |
|
---|
3 | {$mode delphi}{$H+}
|
---|
4 |
|
---|
5 | interface
|
---|
6 |
|
---|
7 | uses
|
---|
8 | Classes, SysUtils, Dialogs, fgl, Graphics, Types, Forms, Math, DateUtils,
|
---|
9 | Controls, URegistry;
|
---|
10 |
|
---|
11 | type
|
---|
12 |
|
---|
13 | { TTile }
|
---|
14 |
|
---|
15 | TTile = class
|
---|
16 | Value: Integer;
|
---|
17 | NewValue: Integer;
|
---|
18 | Merged: Boolean;
|
---|
19 | Moving: Boolean;
|
---|
20 | Shift: TPoint;
|
---|
21 | procedure Assign(Source: TTile);
|
---|
22 | end;
|
---|
23 |
|
---|
24 | TTiles = class(TFPGObjectList<TTile>)
|
---|
25 | end;
|
---|
26 |
|
---|
27 | { TBoard }
|
---|
28 |
|
---|
29 | TBoard = class
|
---|
30 | private
|
---|
31 | FSize: TPoint;
|
---|
32 | procedure SetSize(AValue: TPoint);
|
---|
33 | public
|
---|
34 | Tiles: array of array of TTile;
|
---|
35 | procedure Assign(Source: TBoard);
|
---|
36 | procedure Clear;
|
---|
37 | procedure ClearMerged;
|
---|
38 | function GetHighestTileValue: Integer;
|
---|
39 | function GetEmptyTilesCount: Integer;
|
---|
40 | procedure GetEmptyTiles(EmptyTiles: TTiles);
|
---|
41 | procedure SaveToRegistry(RegContext: TRegistryContext);
|
---|
42 | procedure LoadFromRegistry(RegContext: TRegistryContext);
|
---|
43 | destructor Destroy; override;
|
---|
44 | property Size: TPoint read FSize write SetSize;
|
---|
45 | end;
|
---|
46 |
|
---|
47 | TDirection = (drLeft, drUp, drRight, drDown);
|
---|
48 |
|
---|
49 | { TGame }
|
---|
50 |
|
---|
51 | TGame = class
|
---|
52 | private
|
---|
53 | FMoving: Boolean;
|
---|
54 | FOnChange: TNotifyEvent;
|
---|
55 | FRunning: Boolean;
|
---|
56 | FScore: Integer;
|
---|
57 | FCanUndo: Boolean;
|
---|
58 | FBoardUndo: TBoard;
|
---|
59 | function CanMoveDirection(Direction: TDirection): Boolean;
|
---|
60 | function GetTileColor(Value: Integer): TColor;
|
---|
61 | procedure SetScore(AValue: Integer);
|
---|
62 | procedure DoChange;
|
---|
63 | procedure RenderTile(Canvas: TCanvas; Tile: TTile; TileRect: TRect);
|
---|
64 | procedure GameOver;
|
---|
65 | procedure Win;
|
---|
66 | function FillRandomTile: Integer;
|
---|
67 | public
|
---|
68 | Board: TBoard;
|
---|
69 | TopScore: Integer;
|
---|
70 | AnimationDuration: Integer;
|
---|
71 | WinScore: Integer;
|
---|
72 | function CanUndo: Boolean;
|
---|
73 | procedure Undo;
|
---|
74 | function CanMove: Boolean;
|
---|
75 | procedure Assign(Source: TGame);
|
---|
76 | procedure New;
|
---|
77 | procedure Render(Canvas: TCanvas; CanvasSize: TPoint);
|
---|
78 | function MoveAll(Direction: TDirection): Integer;
|
---|
79 | procedure MoveTile(SourceTile, TargetTile: TTile);
|
---|
80 | function IsValidPos(Pos: TPoint): Boolean;
|
---|
81 | procedure SaveToRegistry(RegContext: TRegistryContext);
|
---|
82 | procedure LoadFromRegistry(RegContext: TRegistryContext);
|
---|
83 | constructor Create;
|
---|
84 | destructor Destroy; override;
|
---|
85 | property Score: Integer read FScore write SetScore;
|
---|
86 | property Running: Boolean read FRunning write FRunning;
|
---|
87 | property OnChange: TNotifyEvent read FOnChange write FOnChange;
|
---|
88 | property Moving: Boolean read FMoving;
|
---|
89 | end;
|
---|
90 |
|
---|
91 | const
|
---|
92 | DirectionDiff: array[TDirection] of TPoint = (
|
---|
93 | (X: -1; Y: 0), (X: 0; Y: -1), (X: 1; Y: 0), (X: 0; Y: 1)
|
---|
94 | );
|
---|
95 |
|
---|
96 | resourcestring
|
---|
97 | SGameOverCaption = 'Lost';
|
---|
98 | SGameOverMessage = 'Game over!';
|
---|
99 | SWinCaption = 'Win';
|
---|
100 | SWinMessage = 'You reached %d and won! You can continue to play to get higher score.';
|
---|
101 | SScore = 'Score';
|
---|
102 | STopScore = 'Top score';
|
---|
103 |
|
---|
104 | implementation
|
---|
105 |
|
---|
106 | { TBoard }
|
---|
107 |
|
---|
108 | procedure TBoard.SetSize(AValue: TPoint);
|
---|
109 | var
|
---|
110 | X, Y: Integer;
|
---|
111 | begin
|
---|
112 | if FSize = AValue then Exit;
|
---|
113 | for Y := 0 to FSize.Y - 1 do
|
---|
114 | for X := 0 to FSize.X - 1 do
|
---|
115 | Tiles[Y, X].Free;
|
---|
116 | FSize := AValue;
|
---|
117 | SetLength(Tiles, FSize.Y, FSize.X);
|
---|
118 | for Y := 0 to FSize.Y - 1 do
|
---|
119 | for X := 0 to FSize.X - 1 do
|
---|
120 | Tiles[Y, X] := TTile.Create;
|
---|
121 | end;
|
---|
122 |
|
---|
123 | procedure TBoard.Assign(Source: TBoard);
|
---|
124 | var
|
---|
125 | X, Y: Integer;
|
---|
126 | begin
|
---|
127 | Size := Source.Size;
|
---|
128 | for Y := 0 to Size.Y - 1 do
|
---|
129 | for X := 0 to Size.X - 1 do
|
---|
130 | Tiles[Y, X].Assign(Source.Tiles[Y, X]);
|
---|
131 | end;
|
---|
132 |
|
---|
133 | procedure TBoard.GetEmptyTiles(EmptyTiles: TTiles);
|
---|
134 | var
|
---|
135 | X, Y: Integer;
|
---|
136 | begin
|
---|
137 | EmptyTiles.Clear;
|
---|
138 | for Y := 0 to Size.Y - 1 do
|
---|
139 | for X := 0 to Size.X - 1 do
|
---|
140 | if Tiles[Y, X].Value = 0 then
|
---|
141 | EmptyTiles.Add(Tiles[Y, X]);
|
---|
142 | end;
|
---|
143 |
|
---|
144 | procedure TBoard.SaveToRegistry(RegContext: TRegistryContext);
|
---|
145 | var
|
---|
146 | X, Y: Integer;
|
---|
147 | Value: string;
|
---|
148 | begin
|
---|
149 | with TRegistryEx.Create do
|
---|
150 | try
|
---|
151 | CurrentContext := RegContext;
|
---|
152 |
|
---|
153 | WriteInteger('SizeX', Size.X);
|
---|
154 | WriteInteger('SizeY', Size.Y);
|
---|
155 | Value := '';
|
---|
156 | for Y := 0 to Size.Y - 1 do begin
|
---|
157 | for X := 0 to Size.X - 1 do begin
|
---|
158 | Value := Value + IntToStr(Tiles[Y, X].Value);
|
---|
159 | if X < Size.X - 1 then Value := Value + ',';
|
---|
160 | end;
|
---|
161 | if Y < Size.Y - 1 then Value := Value + ';'
|
---|
162 | end;
|
---|
163 | WriteString('TileValues', Value);
|
---|
164 | finally
|
---|
165 | Free;
|
---|
166 | end;
|
---|
167 | end;
|
---|
168 |
|
---|
169 | procedure TBoard.LoadFromRegistry(RegContext: TRegistryContext);
|
---|
170 | var
|
---|
171 | X, Y: Integer;
|
---|
172 | Items: TStringList;
|
---|
173 | Lines: TStringList;
|
---|
174 | Number: Integer;
|
---|
175 | begin
|
---|
176 | with TRegistryEx.Create do
|
---|
177 | try
|
---|
178 | CurrentContext := RegContext;
|
---|
179 |
|
---|
180 | Size := Point(ReadIntegerWithDefault('SizeX', 4), ReadIntegerWithDefault('SizeY', 4));
|
---|
181 | Items := TStringList.Create;
|
---|
182 | Items.Delimiter := ',';
|
---|
183 | Lines := TStringList.Create;
|
---|
184 | Lines.Delimiter := ';';
|
---|
185 | Lines.DelimitedText := ReadStringWithDefault('TileValues', '');
|
---|
186 | for Y := 0 to Lines.Count - 1 do begin
|
---|
187 | Items.DelimitedText := Lines[Y];
|
---|
188 | for X := 0 to Items.Count - 1 do begin
|
---|
189 | if TryStrToInt(Items[X], Number) and (X < Size.X) and (Y < Size.Y) then
|
---|
190 | Tiles[Y, X].Value := Number;
|
---|
191 | end;
|
---|
192 | end;
|
---|
193 | finally
|
---|
194 | Free;
|
---|
195 | end;
|
---|
196 | end;
|
---|
197 |
|
---|
198 | destructor TBoard.Destroy;
|
---|
199 | begin
|
---|
200 | Size := Point(0, 0);
|
---|
201 | inherited Destroy;
|
---|
202 | end;
|
---|
203 |
|
---|
204 | procedure TBoard.ClearMerged;
|
---|
205 | var
|
---|
206 | X, Y: Integer;
|
---|
207 | begin
|
---|
208 | for Y := 0 to Size.Y - 1 do
|
---|
209 | for X := 0 to Size.X - 1 do
|
---|
210 | Tiles[Y, X].Merged := False;
|
---|
211 | end;
|
---|
212 |
|
---|
213 | function TBoard.GetEmptyTilesCount: Integer;
|
---|
214 | var
|
---|
215 | X, Y: Integer;
|
---|
216 | begin
|
---|
217 | Result := 0;
|
---|
218 | for Y := 0 to Size.Y - 1 do
|
---|
219 | for X := 0 to Size.X - 1 do
|
---|
220 | if Tiles[Y, X].Value = 0 then
|
---|
221 | Inc(Result);
|
---|
222 | end;
|
---|
223 |
|
---|
224 | function TBoard.GetHighestTileValue: Integer;
|
---|
225 | var
|
---|
226 | X, Y: Integer;
|
---|
227 | begin
|
---|
228 | Result := 0;
|
---|
229 | for Y := 0 to Size.Y - 1 do
|
---|
230 | for X := 0 to Size.X - 1 do
|
---|
231 | if Result < Tiles[Y, X].Value then Result := Tiles[Y, X].Value;
|
---|
232 | end;
|
---|
233 |
|
---|
234 | procedure TBoard.Clear;
|
---|
235 | var
|
---|
236 | X, Y: Integer;
|
---|
237 | begin
|
---|
238 | for Y := 0 to Size.Y - 1 do
|
---|
239 | for X := 0 to Size.X - 1 do
|
---|
240 | Tiles[Y, X].Value := 0;
|
---|
241 | end;
|
---|
242 |
|
---|
243 |
|
---|
244 | { TTile }
|
---|
245 |
|
---|
246 | procedure TTile.Assign(Source: TTile);
|
---|
247 | begin
|
---|
248 | Value := Source.Value;
|
---|
249 | Merged := Source.Merged;
|
---|
250 | end;
|
---|
251 |
|
---|
252 | { TGame }
|
---|
253 |
|
---|
254 | procedure TGame.DoChange;
|
---|
255 | begin
|
---|
256 | if Assigned(FOnChange) then FOnChange(Self);
|
---|
257 | end;
|
---|
258 |
|
---|
259 | procedure TGame.GameOver;
|
---|
260 | begin
|
---|
261 | if Running then MessageDlg(SGameOverCaption, SGameOverMessage, mtInformation, [mbOK], 0);
|
---|
262 | Running := False;
|
---|
263 | end;
|
---|
264 |
|
---|
265 | procedure TGame.Win;
|
---|
266 | begin
|
---|
267 | MessageDlg(SWinCaption, Format(SWinMessage, [WinScore]), mtInformation, [mbOk], 0);
|
---|
268 | end;
|
---|
269 |
|
---|
270 | function TGame.FillRandomTile: Integer;
|
---|
271 | var
|
---|
272 | EmptyTiles: TTiles;
|
---|
273 | begin
|
---|
274 | Result := 0;
|
---|
275 | EmptyTiles := TTiles.Create(False);
|
---|
276 | Board.GetEmptyTiles(EmptyTiles);
|
---|
277 | if EmptyTiles.Count > 0 then begin
|
---|
278 | EmptyTiles[Random(EmptyTiles.Count)].Value := 2;
|
---|
279 | Result := 1;
|
---|
280 | end;
|
---|
281 | EmptyTiles.Free;
|
---|
282 | end;
|
---|
283 |
|
---|
284 | function TGame.CanMove: Boolean;
|
---|
285 | begin
|
---|
286 | Result := CanMoveDirection(drLeft) or CanMoveDirection(drRight) or
|
---|
287 | CanMoveDirection(drUp) or CanMoveDirection(drDown);
|
---|
288 | end;
|
---|
289 |
|
---|
290 | procedure TGame.Assign(Source: TGame);
|
---|
291 | begin
|
---|
292 | FScore := Source.FScore;
|
---|
293 | TopScore := Source.TopScore;
|
---|
294 | AnimationDuration := Source.AnimationDuration;
|
---|
295 | Board.Assign(Source.Board);
|
---|
296 | end;
|
---|
297 |
|
---|
298 | procedure TGame.New;
|
---|
299 | var
|
---|
300 | I: Integer;
|
---|
301 | begin
|
---|
302 | FCanUndo := False;
|
---|
303 | Board.Clear;
|
---|
304 | Score := 0;
|
---|
305 | Running := True;
|
---|
306 | for I := 0 to 1 do FillRandomTile;
|
---|
307 | DoChange;
|
---|
308 | end;
|
---|
309 |
|
---|
310 | procedure TGame.Render(Canvas: TCanvas; CanvasSize: TPoint);
|
---|
311 | var
|
---|
312 | X, Y: Integer;
|
---|
313 | TileSize: TPoint;
|
---|
314 | ValueStr: string;
|
---|
315 | Frame: TRect;
|
---|
316 | TileRect: TRect;
|
---|
317 | TopBarHeight: Integer;
|
---|
318 | TileMargin: Integer;
|
---|
319 | begin
|
---|
320 | TopBarHeight := ScaleY(24, 96);
|
---|
321 | TileMargin := Round(CanvasSize.X / Board.Size.X * 0.02);
|
---|
322 | Canvas.Brush.Style := bsSolid;
|
---|
323 | Canvas.Brush.Color := clBlack;
|
---|
324 | Canvas.FillRect(0, 0, Canvas.Width, Canvas.Height);
|
---|
325 |
|
---|
326 | ValueStr := SScore + ': ' + IntToStr(Score);
|
---|
327 | Canvas.Font.Color := clWhite;
|
---|
328 | Canvas.Font.Height := Trunc(TopBarHeight * 0.7);
|
---|
329 | Canvas.TextOut(ScaleY(16, 96), (TopBarHeight - Canvas.TextHeight(ValueStr)) div 2, ValueStr);
|
---|
330 |
|
---|
331 | ValueStr := STopScore + ': ' + IntToStr(TopScore);
|
---|
332 | Canvas.Font.Color := clWhite;
|
---|
333 | Canvas.Font.Height := Trunc(TopBarHeight * 0.7);
|
---|
334 | Canvas.TextOut(ScaleY(136, 96), (TopBarHeight - Canvas.TextHeight(ValueStr)) div 2, ValueStr);
|
---|
335 |
|
---|
336 | // Form.Canvas.Width and Form.Canvas.Height is not working correctly under Windows.
|
---|
337 | // So dimensions are provided by CanvasSize parameter.
|
---|
338 | Frame := Rect(2, TopBarHeight, CanvasSize.X - 2, CanvasSize.Y - 2);
|
---|
339 | TileSize := Point(Frame.Width div Board.Size.X, Frame.Height div Board.Size.Y);
|
---|
340 | if TileSize.X < TileSize.Y then TileSize.Y := TileSize.X;
|
---|
341 | if TileSize.Y < TileSize.X then TileSize.X := TileSize.Y;
|
---|
342 | Frame := Rect(Frame.Width div 2 - (Board.Size.X * TileSize.X) div 2,
|
---|
343 | Frame.Top + Frame.Height div 2 - (Board.Size.Y * TileSize.Y) div 2,
|
---|
344 | Frame.Width div 2 + (Board.Size.X * TileSize.X) div 2,
|
---|
345 | Frame.Top + Frame.Height div 2 + (Board.Size.Y * TileSize.Y) div 2);
|
---|
346 |
|
---|
347 | Canvas.Brush.Style := bsSolid;
|
---|
348 | Canvas.Brush.Color := clGray;
|
---|
349 | Canvas.FillRect(Frame);
|
---|
350 |
|
---|
351 | Canvas.Font.Color := clBlack;
|
---|
352 |
|
---|
353 | // Draw static tiles
|
---|
354 | for Y := 0 to Board.Size.Y - 1 do
|
---|
355 | for X := 0 to Board.Size.X - 1 do begin
|
---|
356 | if Board.Tiles[Y, X].Moving then Canvas.Brush.Color := GetTileColor(0)
|
---|
357 | else Canvas.Brush.Color := GetTileColor(Board.Tiles[Y, X].Value);
|
---|
358 | Canvas.Brush.Style := bsSolid;
|
---|
359 | TileRect := Bounds(
|
---|
360 | Frame.Left + X * TileSize.X + TileMargin,
|
---|
361 | Frame.Top + Y * TileSize.Y + TileMargin,
|
---|
362 | TileSize.X - 2 * TileMargin, TileSize.Y - 2 * TileMargin);
|
---|
363 | RenderTile(Canvas, Board.Tiles[Y, X], TileRect);
|
---|
364 | end;
|
---|
365 |
|
---|
366 | // Draw moving Tiles
|
---|
367 | for Y := 0 to Board.Size.Y - 1 do
|
---|
368 | for X := 0 to Board.Size.X - 1 do
|
---|
369 | if Board.Tiles[Y, X].Moving then begin
|
---|
370 | Canvas.Brush.Color := GetTileColor(Board.Tiles[Y, X].Value);
|
---|
371 | Canvas.Brush.Style := bsSolid;
|
---|
372 | TileRect := Bounds(
|
---|
373 | Frame.Left + X * TileSize.X + Trunc(Board.Tiles[Y, X].Shift.X / 100 * TileSize.X + TileMargin),
|
---|
374 | Frame.Top + Y * TileSize.Y + Trunc(Board.Tiles[Y, X].Shift.Y / 100 * TileSize.Y + TileMargin),
|
---|
375 | TileSize.X - 2 * TileMargin, TileSize.Y - 2 * TileMargin);
|
---|
376 | RenderTile(Canvas, Board.Tiles[Y, X], TileRect);
|
---|
377 | end;
|
---|
378 | end;
|
---|
379 |
|
---|
380 | procedure TGame.RenderTile(Canvas: TCanvas; Tile: TTile; TileRect: TRect);
|
---|
381 | var
|
---|
382 | ValueStr: string;
|
---|
383 | TextSize: TSize;
|
---|
384 | begin
|
---|
385 | Canvas.FillRect(TileRect);
|
---|
386 | if Tile.Value <> 0 then begin
|
---|
387 | ValueStr := IntToStr(Tile.Value);
|
---|
388 | Canvas.Brush.Style := bsClear;
|
---|
389 | Canvas.Font.Height := Trunc(TileRect.Height * 0.7);
|
---|
390 | TextSize := Canvas.TextExtent(ValueStr);
|
---|
391 | if TextSize.Width > TileRect.Width then
|
---|
392 | Canvas.Font.Height := Trunc(Canvas.Font.Height / TextSize.Width * TileRect.Width);
|
---|
393 | TextSize := Canvas.TextExtent(ValueStr);
|
---|
394 | Canvas.TextOut(TileRect.Left + TileRect.Width div 2 - TextSize.Width div 2,
|
---|
395 | TileRect.Top + TileRect.Height div 2 - TextSize.Height div 2, ValueStr);
|
---|
396 | end;
|
---|
397 | end;
|
---|
398 |
|
---|
399 | function TGame.CanUndo: Boolean;
|
---|
400 | begin
|
---|
401 | Result := FCanUndo;
|
---|
402 | end;
|
---|
403 |
|
---|
404 | procedure TGame.Undo;
|
---|
405 | begin
|
---|
406 | if CanUndo then begin
|
---|
407 | Board.Assign(FBoardUndo);
|
---|
408 | FCanUndo := False;
|
---|
409 | FRunning := CanMove;
|
---|
410 | DoChange;
|
---|
411 | end;
|
---|
412 | end;
|
---|
413 |
|
---|
414 | function TGame.CanMoveDirection(Direction: TDirection): Boolean;
|
---|
415 | var
|
---|
416 | StartPoint: TPoint;
|
---|
417 | AreaSize: TPoint;
|
---|
418 | Increment: TPoint;
|
---|
419 | P: TPoint;
|
---|
420 | PNew: TPoint;
|
---|
421 | PI: TPoint;
|
---|
422 | begin
|
---|
423 | Result := False;
|
---|
424 | case Direction of
|
---|
425 | drLeft: begin
|
---|
426 | StartPoint := Point(1, 0);
|
---|
427 | AreaSize := Point(Board.Size.X - 2, Board.Size.Y - 1);
|
---|
428 | Increment := Point(1, 1);
|
---|
429 | end;
|
---|
430 | drUp: begin
|
---|
431 | StartPoint := Point(0, 1);
|
---|
432 | AreaSize := Point(Board.Size.X - 1, Board.Size.Y - 2);
|
---|
433 | Increment := Point(1, 1);
|
---|
434 | end;
|
---|
435 | drRight: begin
|
---|
436 | StartPoint := Point(Board.Size.X - 2, 0);
|
---|
437 | AreaSize := Point(Board.Size.X - 2, Board.Size.Y - 1);
|
---|
438 | Increment := Point(-1, 1);
|
---|
439 | end;
|
---|
440 | drDown: begin
|
---|
441 | StartPoint := Point(0, Board.Size.Y - 2);
|
---|
442 | AreaSize := Point(Board.Size.X - 1, Board.Size.Y - 2);
|
---|
443 | Increment := Point(1, -1);
|
---|
444 | end;
|
---|
445 | end;
|
---|
446 |
|
---|
447 | PI.Y := 0;
|
---|
448 | while PI.Y <= AreaSize.Y do begin
|
---|
449 | PI.X := 0;
|
---|
450 | while PI.X <= AreaSize.X do begin
|
---|
451 | P := Point(StartPoint.X + PI.X * Increment.X, StartPoint.Y + PI.Y * Increment.Y);
|
---|
452 | PNew.X := P.X + DirectionDiff[Direction].X;
|
---|
453 | PNew.Y := P.Y + DirectionDiff[Direction].Y;
|
---|
454 | if IsValidPos(PNew) then begin
|
---|
455 | if (Board.Tiles[P.Y, P.X].Value <> 0) then begin
|
---|
456 | if (Board.Tiles[PNew.Y, PNew.X].Value = 0) or
|
---|
457 | (Board.Tiles[PNew.Y, PNew.X].Value = Board.Tiles[P.Y, P.X].Value) then begin
|
---|
458 | Result := True;
|
---|
459 | Break;
|
---|
460 | end;
|
---|
461 | end;
|
---|
462 | P.X := PNew.X;
|
---|
463 | P.Y := PNew.Y;
|
---|
464 | PNew.X := P.X + DirectionDiff[Direction].X;
|
---|
465 | PNew.Y := P.Y + DirectionDiff[Direction].Y;
|
---|
466 | end;
|
---|
467 | Inc(PI.X);
|
---|
468 | end;
|
---|
469 | if Result then Break;
|
---|
470 | Inc(PI.Y);
|
---|
471 | end;
|
---|
472 | end;
|
---|
473 |
|
---|
474 | function TGame.MoveAll(Direction: TDirection): Integer;
|
---|
475 | var
|
---|
476 | StartPoint: TPoint;
|
---|
477 | AreaSize: TPoint;
|
---|
478 | Increment: TPoint;
|
---|
479 | MoveDirection: TPoint;
|
---|
480 | P: TPoint;
|
---|
481 | PNew: TPoint;
|
---|
482 | PI: TPoint;
|
---|
483 | MovedCount: Integer;
|
---|
484 | X, Y: Integer;
|
---|
485 | I: Integer;
|
---|
486 | StartTime: TDateTime;
|
---|
487 | EndTime: TDateTime;
|
---|
488 | Time: TDateTime;
|
---|
489 | Part: Double;
|
---|
490 | HighestValue: Integer;
|
---|
491 | begin
|
---|
492 | FMoving := True;
|
---|
493 | HighestValue := Board.GetHighestTileValue;
|
---|
494 | FBoardUndo.Assign(Board);
|
---|
495 | FCanUndo := True;
|
---|
496 | //Diff := DirectionDiff[Direction];
|
---|
497 | case Direction of
|
---|
498 | drLeft: begin
|
---|
499 | StartPoint := Point(1, 0);
|
---|
500 | AreaSize := Point(Board.Size.X - 2, Board.Size.Y - 1);
|
---|
501 | Increment := Point(1, 1);
|
---|
502 | MoveDirection := Point(-1, 0);
|
---|
503 | end;
|
---|
504 | drUp: begin
|
---|
505 | StartPoint := Point(0, 1);
|
---|
506 | AreaSize := Point(Board.Size.X - 1, Board.Size.Y - 2);
|
---|
507 | Increment := Point(1, 1);
|
---|
508 | MoveDirection := Point(0, -1);
|
---|
509 | end;
|
---|
510 | drRight: begin
|
---|
511 | StartPoint := Point(Board.Size.X - 2, 0);
|
---|
512 | AreaSize := Point(Board.Size.X - 2, Board.Size.Y - 1);
|
---|
513 | Increment := Point(-1, 1);
|
---|
514 | MoveDirection := Point(1, 0);
|
---|
515 | end;
|
---|
516 | drDown: begin
|
---|
517 | StartPoint := Point(0, Board.Size.Y - 2);
|
---|
518 | AreaSize := Point(Board.Size.X - 1, Board.Size.Y - 2);
|
---|
519 | Increment := Point(1, -1);
|
---|
520 | MoveDirection := Point(0, 1);
|
---|
521 | end;
|
---|
522 | end;
|
---|
523 | MovedCount := 0;
|
---|
524 | Board.ClearMerged;
|
---|
525 | for I := 0 to Max(Board.Size.X, Board.Size.Y) - 1 do begin
|
---|
526 | PI.Y := 0;
|
---|
527 | for Y := 0 to Board.Size.Y - 1 do
|
---|
528 | for X := 0 to Board.Size.X - 1 do begin
|
---|
529 | Board.Tiles[Y, X].NewValue := Board.Tiles[Y, X].Value;
|
---|
530 | Board.Tiles[Y, X].Moving := False;
|
---|
531 | end;
|
---|
532 |
|
---|
533 | while PI.Y <= AreaSize.Y do begin
|
---|
534 | PI.X := 0;
|
---|
535 | while PI.X <= AreaSize.X do begin
|
---|
536 | P := Point(StartPoint.X + PI.X * Increment.X, StartPoint.Y + PI.Y * Increment.Y);
|
---|
537 | PNew.X := P.X + DirectionDiff[Direction].X;
|
---|
538 | PNew.Y := P.Y + DirectionDiff[Direction].Y;
|
---|
539 | if IsValidPos(PNew) then begin
|
---|
540 | if (Board.Tiles[P.Y, P.X].NewValue <> 0) then begin
|
---|
541 | if (Board.Tiles[PNew.Y, PNew.X].NewValue = 0) then begin
|
---|
542 | Board.Tiles[P.Y, P.X].Moving := True;
|
---|
543 | Board.Tiles[PNew.Y, PNew.X].NewValue := Board.Tiles[P.Y, P.X].NewValue;
|
---|
544 | Board.Tiles[PNew.Y, PNew.X].Merged := Board.Tiles[P.Y, P.X].Merged;
|
---|
545 | Board.Tiles[P.Y, P.X].NewValue := 0;
|
---|
546 | Board.Tiles[P.Y, P.X].Merged := False;
|
---|
547 | Inc(MovedCount);
|
---|
548 | end else
|
---|
549 | if (not Board.Tiles[P.Y, P.X].Merged) and (not Board.Tiles[PNew.Y, PNew.X].Merged) and
|
---|
550 | (Board.Tiles[PNew.Y, PNew.X].NewValue = Board.Tiles[P.Y, P.X].NewValue) then begin
|
---|
551 | Board.Tiles[P.Y, P.X].Moving := True;
|
---|
552 | Board.Tiles[PNew.Y, PNew.X].NewValue := Board.Tiles[PNew.Y, PNew.X].NewValue + Board.Tiles[P.Y, P.X].NewValue;
|
---|
553 | Board.Tiles[PNew.Y, PNew.X].Merged := True;
|
---|
554 | Board.Tiles[P.Y, P.X].NewValue := 0;
|
---|
555 | Board.Tiles[P.Y, P.X].Merged := False;
|
---|
556 | Inc(MovedCount);
|
---|
557 | Score := Score + Board.Tiles[PNew.Y, PNew.X].NewValue;
|
---|
558 | end;
|
---|
559 | end;
|
---|
560 | P.X := PNew.X;
|
---|
561 | P.Y := PNew.Y;
|
---|
562 | PNew.X := P.X + DirectionDiff[Direction].X;
|
---|
563 | PNew.Y := P.Y + DirectionDiff[Direction].Y;
|
---|
564 | end;
|
---|
565 | Inc(PI.X);
|
---|
566 | end;
|
---|
567 | Inc(PI.Y);
|
---|
568 | end;
|
---|
569 |
|
---|
570 | // Animate tiles move
|
---|
571 | StartTime := Now;
|
---|
572 | EndTime := StartTime + AnimationDuration / 300 * OneSecond / Max(Board.Size.X, Board.Size.Y);
|
---|
573 | if AnimationDuration > 0 then
|
---|
574 | repeat
|
---|
575 | Time := Now;
|
---|
576 | Part := (Time - StartTime) / (EndTime - StartTime);
|
---|
577 | if Part > 1 then Part := 1;
|
---|
578 | for Y := 0 to Board.Size.Y - 1 do
|
---|
579 | for X := 0 to Board.Size.X - 1 do begin
|
---|
580 | if Board.Tiles[Y, X].Moving then
|
---|
581 | Board.Tiles[Y, X].Shift := Point(Trunc(Part * MoveDirection.X * 100),
|
---|
582 | Trunc(Part * MoveDirection.Y * 100));
|
---|
583 | end;
|
---|
584 | DoChange;
|
---|
585 | Application.ProcessMessages;
|
---|
586 | Sleep(1);
|
---|
587 | until Time > EndTime;
|
---|
588 |
|
---|
589 | // Set final tiles values
|
---|
590 | for Y := 0 to Board.Size.Y - 1 do
|
---|
591 | for X := 0 to Board.Size.X - 1 do begin
|
---|
592 | Board.Tiles[Y, X].Shift := Point(0, 0);
|
---|
593 | Board.Tiles[Y, X].Moving := False;
|
---|
594 | Board.Tiles[Y, X].Value := Board.Tiles[Y, X].NewValue;
|
---|
595 | end;
|
---|
596 | DoChange;
|
---|
597 | end;
|
---|
598 | Result := MovedCount;
|
---|
599 |
|
---|
600 | // Update state after move
|
---|
601 | if MovedCount > 0 then FillRandomTile;
|
---|
602 | if not CanMove and (Board.GetEmptyTilesCount = 0) then
|
---|
603 | GameOver;
|
---|
604 | if (HighestValue < WinScore) and
|
---|
605 | (Board.GetHighestTileValue >= WinScore) then Win;
|
---|
606 |
|
---|
607 | FMoving := False;
|
---|
608 | end;
|
---|
609 |
|
---|
610 | procedure TGame.MoveTile(SourceTile, TargetTile: TTile);
|
---|
611 | begin
|
---|
612 | TargetTile.Value := SourceTile.Value;
|
---|
613 | SourceTile.Value := 0;
|
---|
614 | TargetTile.Merged := SourceTile.Merged;
|
---|
615 | SourceTile.Merged := False;
|
---|
616 | end;
|
---|
617 |
|
---|
618 | function TGame.IsValidPos(Pos: TPoint): Boolean;
|
---|
619 | begin
|
---|
620 | Result := (Pos.X >= 0) and (Pos.X < Board.Size.X) and
|
---|
621 | (Pos.Y >= 0) and (Pos.Y < Board.Size.Y);
|
---|
622 | end;
|
---|
623 |
|
---|
624 | procedure TGame.SaveToRegistry(RegContext: TRegistryContext);
|
---|
625 | begin
|
---|
626 | with TRegistryEx.Create do
|
---|
627 | try
|
---|
628 | CurrentContext := RegContext;
|
---|
629 |
|
---|
630 | WriteInteger('TopScore', TopScore);
|
---|
631 | WriteInteger('AnimationDuration', AnimationDuration);
|
---|
632 | WriteInteger('Score', Score);
|
---|
633 | WriteBool('GameRunning', FRunning);
|
---|
634 | WriteBool('CanUndo', FCanUndo);
|
---|
635 | finally
|
---|
636 | Free;
|
---|
637 | end;
|
---|
638 | FBoardUndo.SaveToRegistry(TRegistryContext.Create(RegContext.RootKey, RegContext.Key + '\BoardUndo'));
|
---|
639 | Board.SaveToRegistry(TRegistryContext.Create(RegContext.RootKey, RegContext.Key + '\Board'));
|
---|
640 | end;
|
---|
641 |
|
---|
642 | procedure TGame.LoadFromRegistry(RegContext: TRegistryContext);
|
---|
643 | begin
|
---|
644 | with TRegistryEx.Create do
|
---|
645 | try
|
---|
646 | CurrentContext := RegContext;
|
---|
647 | AnimationDuration := ReadIntegerWithDefault('AnimationDuration', 30);
|
---|
648 | TopScore := ReadIntegerWithDefault('TopScore', 0);
|
---|
649 | Score := ReadIntegerWithDefault('Score', 0);
|
---|
650 | FRunning := ReadBoolWithDefault('GameRunning', False);
|
---|
651 | FCanUndo := ReadBoolWithDefault('CanUndo', False);
|
---|
652 | finally
|
---|
653 | Free;
|
---|
654 | end;
|
---|
655 | FBoardUndo.LoadFromRegistry(TRegistryContext.Create(RegContext.RootKey, RegContext.Key + '\BoardUndo'));
|
---|
656 | Board.LoadFromRegistry(TRegistryContext.Create(RegContext.RootKey, RegContext.Key + '\Board'));
|
---|
657 | DoChange;
|
---|
658 | end;
|
---|
659 |
|
---|
660 | constructor TGame.Create;
|
---|
661 | begin
|
---|
662 | AnimationDuration := 30;
|
---|
663 | WinScore := 2048;
|
---|
664 | Board := TBoard.Create;
|
---|
665 | FBoardUndo := TBoard.Create;
|
---|
666 | end;
|
---|
667 |
|
---|
668 | destructor TGame.Destroy;
|
---|
669 | begin
|
---|
670 | FreeAndNil(FBoardUndo);
|
---|
671 | FreeAndNil(Board);
|
---|
672 | inherited;
|
---|
673 | end;
|
---|
674 |
|
---|
675 | function TGame.GetTileColor(Value: Integer): TColor;
|
---|
676 | begin
|
---|
677 | case Value of
|
---|
678 | 0: Result := $f2f6f9;
|
---|
679 | 2: Result := $dae4ee;
|
---|
680 | 4: Result := $c8e0ed;
|
---|
681 | 8: Result := $79b1f2;
|
---|
682 | 16: Result := $6395f5;
|
---|
683 | 32: Result := $5f7cf6;
|
---|
684 | 64: Result := $3b5ef6;
|
---|
685 | 128: Result := $72cfed;
|
---|
686 | 256: Result := $61cced;
|
---|
687 | 512: Result := $50c8ed;
|
---|
688 | 1024: Result := $3fc5ed;
|
---|
689 | 2048: Result := $2ec2ed;
|
---|
690 | else Result := $323a3c;
|
---|
691 | end;
|
---|
692 | end;
|
---|
693 |
|
---|
694 | procedure TGame.SetScore(AValue: Integer);
|
---|
695 | begin
|
---|
696 | if FScore = AValue then Exit;
|
---|
697 | FScore := AValue;
|
---|
698 | if FScore > TopScore then TopScore := FScore;
|
---|
699 | end;
|
---|
700 |
|
---|
701 | end.
|
---|
702 |
|
---|