Changeset 17 for trunk/UGame.pas
- Timestamp:
- Oct 5, 2019, 12:17:17 PM (5 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/UGame.pas
r16 r17 11 11 type 12 12 13 { T Cell}14 15 T Cell= class13 { TTile } 14 15 TTile = class 16 16 Value: Integer; 17 17 NewValue: Integer; … … 19 19 Moving: Boolean; 20 20 Shift: TPoint; 21 procedure Assign(Source: TCell); 22 end; 23 24 TCells = class(TFPGObjectList<TCell>) 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 destructor Destroy; override; 42 property Size: TPoint read FSize write SetSize; 25 43 end; 26 44 … … 35 53 FRunning: Boolean; 36 54 FScore: Integer; 37 FSize: TPoint; 38 function GetCellColor(Value: Integer): TColor; 55 function GetTileColor(Value: Integer): TColor; 39 56 procedure SetScore(AValue: Integer); 40 procedure SetSize(AValue: TPoint);41 procedure GetEmptyCells(EmptyCells: TCells);42 57 procedure DoChange; 43 procedure ClearMerged; 44 procedure RenderCell(Canvas: TCanvas; Cell: TCell; CellRect: TRect); 58 procedure RenderTile(Canvas: TCanvas; Tile: TTile; TileRect: TRect); 45 59 public 46 Cells: array of array of TCell;60 Board: TBoard; 47 61 TopScore: Integer; 48 62 AnimationDuration: Integer; … … 50 64 procedure GameOver; 51 65 procedure Win; 52 function FillRandom Cell: Integer;66 function FillRandomTile: Integer; 53 67 function CanMove: Boolean; 54 function GetEmptyCellsCount: Integer;55 function GetHighestCellValue: Integer;56 68 procedure Assign(Source: TGame); 57 69 procedure New; 58 procedure Clear;59 70 procedure Render(Canvas: TCanvas; CanvasSize: TPoint); 60 71 function MoveAll(Direction: TDirection): Integer; 61 procedure Move Cell(SourceCell, TargetCell: TCell);72 procedure MoveTile(SourceTile, TargetTile: TTile); 62 73 function IsValidPos(Pos: TPoint): Boolean; 63 74 constructor Create; 64 75 destructor Destroy; override; 65 76 property Score: Integer read FScore write SetScore; 66 property Size: TPoint read FSize write SetSize;67 77 property Running: Boolean read FRunning write FRunning; 68 78 property OnChange: TNotifyEvent read FOnChange write FOnChange; … … 85 95 implementation 86 96 87 { TCell } 88 89 procedure TCell.Assign(Source: TCell); 90 begin 91 Value := Source.Value; 92 Merged := Source.Merged; 93 end; 94 95 { TGame } 96 97 procedure TGame.SetSize(AValue: TPoint); 97 { TBoard } 98 99 procedure TBoard.SetSize(AValue: TPoint); 98 100 var 99 101 X, Y: Integer; … … 102 104 for Y := 0 to FSize.Y - 1 do 103 105 for X := 0 to FSize.X - 1 do 104 Cells[Y, X].Free;106 Tiles[Y, X].Free; 105 107 FSize := AValue; 106 SetLength( Cells, FSize.Y, FSize.X);108 SetLength(Tiles, FSize.Y, FSize.X); 107 109 for Y := 0 to FSize.Y - 1 do 108 110 for X := 0 to FSize.X - 1 do 109 Cells[Y, X] := TCell.Create;110 end; 111 112 procedure T Game.GetEmptyCells(EmptyCells: TCells);113 var 114 X, Y: Integer; 115 begin 116 EmptyCells.Clear;111 Tiles[Y, X] := TTile.Create; 112 end; 113 114 procedure TBoard.Assign(Source: TBoard); 115 var 116 X, Y: Integer; 117 begin 118 Size := Source.Size; 117 119 for Y := 0 to Size.Y - 1 do 118 120 for X := 0 to Size.X - 1 do 119 if Cells[Y, X].Value = 0 then 120 EmptyCells.Add(Cells[Y, X]); 121 end; 122 123 procedure TGame.DoChange; 124 begin 125 if Assigned(FOnChange) then FOnChange(Self); 126 end; 127 128 procedure TGame.ClearMerged; 129 var 130 X, Y: Integer; 131 begin 121 Tiles[Y, X].Assign(Source.Tiles[Y, X]); 122 end; 123 124 procedure TBoard.GetEmptyTiles(EmptyTiles: TTiles); 125 var 126 X, Y: Integer; 127 begin 128 EmptyTiles.Clear; 132 129 for Y := 0 to Size.Y - 1 do 133 130 for X := 0 to Size.X - 1 do 134 Cells[Y, X].Merged := False; 131 if Tiles[Y, X].Value = 0 then 132 EmptyTiles.Add(Tiles[Y, X]); 133 end; 134 135 destructor TBoard.Destroy; 136 begin 137 Size := Point(0, 0); 138 inherited Destroy; 139 end; 140 141 procedure TBoard.ClearMerged; 142 var 143 X, Y: Integer; 144 begin 145 for Y := 0 to Size.Y - 1 do 146 for X := 0 to Size.X - 1 do 147 Tiles[Y, X].Merged := False; 148 end; 149 150 function TBoard.GetEmptyTilesCount: Integer; 151 var 152 X, Y: Integer; 153 begin 154 Result := 0; 155 for Y := 0 to Size.Y - 1 do 156 for X := 0 to Size.X - 1 do 157 if Tiles[Y, X].Value = 0 then 158 Inc(Result); 159 end; 160 161 function TBoard.GetHighestTileValue: Integer; 162 var 163 X, Y: Integer; 164 begin 165 Result := 0; 166 for Y := 0 to Size.Y - 1 do 167 for X := 0 to Size.X - 1 do 168 if Result < Tiles[Y, X].Value then Result := Tiles[Y, X].Value; 169 end; 170 171 procedure TBoard.Clear; 172 var 173 X, Y: Integer; 174 begin 175 for Y := 0 to Size.Y - 1 do 176 for X := 0 to Size.X - 1 do 177 Tiles[Y, X].Value := 0; 178 end; 179 180 181 { TTile } 182 183 procedure TTile.Assign(Source: TTile); 184 begin 185 Value := Source.Value; 186 Merged := Source.Merged; 187 end; 188 189 { TGame } 190 191 procedure TGame.DoChange; 192 begin 193 if Assigned(FOnChange) then FOnChange(Self); 135 194 end; 136 195 … … 152 211 end; 153 212 154 function TGame.FillRandom Cell: Integer;155 var 156 Empty Cells: TCells;213 function TGame.FillRandomTile: Integer; 214 var 215 EmptyTiles: TTiles; 157 216 begin 158 217 Result := 0; 159 Empty Cells := TCells.Create(False);160 GetEmptyCells(EmptyCells);161 if Empty Cells.Count > 0 then begin162 Empty Cells[Random(EmptyCells.Count)].Value := 2;218 EmptyTiles := TTiles.Create(False); 219 Board.GetEmptyTiles(EmptyTiles); 220 if EmptyTiles.Count > 0 then begin 221 EmptyTiles[Random(EmptyTiles.Count)].Value := 2; 163 222 Result := 1; 164 223 end; 165 Empty Cells.Free;224 EmptyTiles.Free; 166 225 end; 167 226 … … 187 246 end; 188 247 189 function TGame.GetEmptyCellsCount: Integer;190 var191 X, Y: Integer;192 begin193 Result := 0;194 for Y := 0 to Size.Y - 1 do195 for X := 0 to Size.X - 1 do196 if Cells[Y, X].Value = 0 then197 Inc(Result);198 end;199 200 function TGame.GetHighestCellValue: Integer;201 var202 X, Y: Integer;203 begin204 Result := 0;205 for Y := 0 to Size.Y - 1 do206 for X := 0 to Size.X - 1 do207 if Result < Cells[Y, X].Value then Result := Cells[Y, X].Value;208 end;209 210 248 procedure TGame.Assign(Source: TGame); 211 249 var … … 216 254 AnimationDuration := Source.AnimationDuration; 217 255 Won := Source.Won; 218 Size := Source.Size; 219 for Y := 0 to Size.Y - 1 do 220 for X := 0 to Size.X - 1 do 221 Cells[Y, X].Assign(Source.Cells[Y, X]); 256 Board.Assign(Source.Board); 222 257 end; 223 258 … … 226 261 I: Integer; 227 262 begin 228 Clear;263 Board.Clear; 229 264 Score := 0; 230 265 Won := False; 231 266 Running := True; 232 for I := 0 to 1 do FillRandom Cell;267 for I := 0 to 1 do FillRandomTile; 233 268 DoChange; 234 269 end; 235 270 236 procedure TGame.Clear;237 var238 X, Y: Integer;239 begin240 for Y := 0 to Size.Y - 1 do241 for X := 0 to Size.X - 1 do242 Cells[Y, X].Value := 0;243 end;244 245 271 procedure TGame.Render(Canvas: TCanvas; CanvasSize: TPoint); 246 272 var 247 273 X, Y: Integer; 248 CellSize: TPoint;274 TileSize: TPoint; 249 275 ValueStr: string; 250 276 Frame: TRect; 251 CellRect: TRect;277 TileRect: TRect; 252 278 TopBarHeight: Integer; 253 CellMargin: Integer;279 TileMargin: Integer; 254 280 begin 255 281 TopBarHeight := ScaleY(24, 96); 256 CellMargin := Round(CanvasSize.X /Size.X * 0.02);282 TileMargin := Round(CanvasSize.X / Board.Size.X * 0.02); 257 283 Canvas.Brush.Style := bsSolid; 258 284 Canvas.Brush.Color := clBlack; … … 272 298 // So dimensions are provided by CanvasSize parameter. 273 299 Frame := Rect(2, TopBarHeight, CanvasSize.X - 2, CanvasSize.Y - 2); 274 CellSize := Point(Frame.Width div Size.X, Frame.Height divSize.Y);275 if CellSize.X < CellSize.Y then CellSize.Y := CellSize.X;276 if CellSize.Y < CellSize.X then CellSize.X := CellSize.Y;277 Frame := Rect(Frame.Width div 2 - ( Size.X * CellSize.X) div 2,278 Frame.Top + Frame.Height div 2 - ( Size.Y * CellSize.Y) div 2,279 Frame.Width div 2 + ( Size.X * CellSize.X) div 2,280 Frame.Top + Frame.Height div 2 + ( Size.Y * CellSize.Y) div 2);300 TileSize := Point(Frame.Width div Board.Size.X, Frame.Height div Board.Size.Y); 301 if TileSize.X < TileSize.Y then TileSize.Y := TileSize.X; 302 if TileSize.Y < TileSize.X then TileSize.X := TileSize.Y; 303 Frame := Rect(Frame.Width div 2 - (Board.Size.X * TileSize.X) div 2, 304 Frame.Top + Frame.Height div 2 - (Board.Size.Y * TileSize.Y) div 2, 305 Frame.Width div 2 + (Board.Size.X * TileSize.X) div 2, 306 Frame.Top + Frame.Height div 2 + (Board.Size.Y * TileSize.Y) div 2); 281 307 282 308 Canvas.Brush.Style := bsSolid; … … 286 312 Canvas.Font.Color := clBlack; 287 313 288 // Draw static cells289 for Y := 0 to Size.Y - 1 do290 for X := 0 to Size.X - 1 do begin291 if Cells[Y, X].Moving then Canvas.Brush.Color := GetCellColor(0)292 else Canvas.Brush.Color := Get CellColor(Cells[Y, X].Value);314 // Draw static tiles 315 for Y := 0 to Board.Size.Y - 1 do 316 for X := 0 to Board.Size.X - 1 do begin 317 if Board.Tiles[Y, X].Moving then Canvas.Brush.Color := GetTileColor(0) 318 else Canvas.Brush.Color := GetTileColor(Board.Tiles[Y, X].Value); 293 319 Canvas.Brush.Style := bsSolid; 294 CellRect := Bounds(295 Frame.Left + X * CellSize.X + CellMargin,296 Frame.Top + Y * CellSize.Y + CellMargin,297 CellSize.X - 2 * CellMargin, CellSize.Y - 2 * CellMargin);298 Render Cell(Canvas, Cells[Y, X], CellRect);299 end; 300 301 // Draw moving cells302 for Y := 0 to Size.Y - 1 do303 for X := 0 to Size.X - 1 do304 if Cells[Y, X].Moving then begin305 Canvas.Brush.Color := Get CellColor(Cells[Y, X].Value);320 TileRect := Bounds( 321 Frame.Left + X * TileSize.X + TileMargin, 322 Frame.Top + Y * TileSize.Y + TileMargin, 323 TileSize.X - 2 * TileMargin, TileSize.Y - 2 * TileMargin); 324 RenderTile(Canvas, Board.Tiles[Y, X], TileRect); 325 end; 326 327 // Draw moving Tiles 328 for Y := 0 to Board.Size.Y - 1 do 329 for X := 0 to Board.Size.X - 1 do 330 if Board.Tiles[Y, X].Moving then begin 331 Canvas.Brush.Color := GetTileColor(Board.Tiles[Y, X].Value); 306 332 Canvas.Brush.Style := bsSolid; 307 CellRect := Bounds(308 Frame.Left + X * CellSize.X + Trunc(Cells[Y, X].Shift.X / 100 * CellSize.X + CellMargin),309 Frame.Top + Y * CellSize.Y + Trunc(Cells[Y, X].Shift.Y / 100 * CellSize.Y + CellMargin),310 CellSize.X - 2 * CellMargin, CellSize.Y - 2 * CellMargin);311 Render Cell(Canvas, Cells[Y, X], CellRect);312 end; 313 end; 314 315 procedure TGame.Render Cell(Canvas: TCanvas; Cell: TCell; CellRect: TRect);333 TileRect := Bounds( 334 Frame.Left + X * TileSize.X + Trunc(Board.Tiles[Y, X].Shift.X / 100 * TileSize.X + TileMargin), 335 Frame.Top + Y * TileSize.Y + Trunc(Board.Tiles[Y, X].Shift.Y / 100 * TileSize.Y + TileMargin), 336 TileSize.X - 2 * TileMargin, TileSize.Y - 2 * TileMargin); 337 RenderTile(Canvas, Board.Tiles[Y, X], TileRect); 338 end; 339 end; 340 341 procedure TGame.RenderTile(Canvas: TCanvas; Tile: TTile; TileRect: TRect); 316 342 var 317 343 ValueStr: string; 318 344 TextSize: TSize; 319 345 begin 320 Canvas.FillRect( CellRect);321 if Cell.Value <> 0 then begin322 ValueStr := IntToStr( Cell.Value);346 Canvas.FillRect(TileRect); 347 if Tile.Value <> 0 then begin 348 ValueStr := IntToStr(Tile.Value); 323 349 Canvas.Brush.Style := bsClear; 324 Canvas.Font.Height := Trunc( CellRect.Height * 0.7);350 Canvas.Font.Height := Trunc(TileRect.Height * 0.7); 325 351 TextSize := Canvas.TextExtent(ValueStr); 326 if TextSize.Width > CellRect.Width then327 Canvas.Font.Height := Trunc(Canvas.Font.Height / TextSize.Width * CellRect.Width);352 if TextSize.Width > TileRect.Width then 353 Canvas.Font.Height := Trunc(Canvas.Font.Height / TextSize.Width * TileRect.Width); 328 354 TextSize := Canvas.TextExtent(ValueStr); 329 Canvas.TextOut( CellRect.Left + CellRect.Width div 2 - TextSize.Width div 2,330 CellRect.Top + CellRect.Height div 2 - TextSize.Height div 2, ValueStr);355 Canvas.TextOut(TileRect.Left + TileRect.Width div 2 - TextSize.Width div 2, 356 TileRect.Top + TileRect.Height div 2 - TextSize.Height div 2, ValueStr); 331 357 end; 332 358 end; … … 355 381 drLeft: begin 356 382 StartPoint := Point(1, 0); 357 AreaSize := Point( Size.X - 2,Size.Y - 1);383 AreaSize := Point(Board.Size.X - 2, Board.Size.Y - 1); 358 384 Increment := Point(1, 1); 359 385 MoveDirection := Point(-1, 0); … … 361 387 drUp: begin 362 388 StartPoint := Point(0, 1); 363 AreaSize := Point( Size.X - 1,Size.Y - 2);389 AreaSize := Point(Board.Size.X - 1, Board.Size.Y - 2); 364 390 Increment := Point(1, 1); 365 391 MoveDirection := Point(0, -1); 366 392 end; 367 393 drRight: begin 368 StartPoint := Point( Size.X - 2, 0);369 AreaSize := Point( Size.X - 2,Size.Y - 1);394 StartPoint := Point(Board.Size.X - 2, 0); 395 AreaSize := Point(Board.Size.X - 2, Board.Size.Y - 1); 370 396 Increment := Point(-1, 1); 371 397 MoveDirection := Point(1, 0); 372 398 end; 373 399 drDown: begin 374 StartPoint := Point(0, Size.Y - 2);375 AreaSize := Point( Size.X - 1,Size.Y - 2);400 StartPoint := Point(0, Board.Size.Y - 2); 401 AreaSize := Point(Board.Size.X - 1, Board.Size.Y - 2); 376 402 Increment := Point(1, -1); 377 403 MoveDirection := Point(0, 1); … … 379 405 end; 380 406 MovedCount := 0; 381 ClearMerged;382 for I := 0 to Max( Size.X,Size.Y) - 1 do begin407 Board.ClearMerged; 408 for I := 0 to Max(Board.Size.X, Board.Size.Y) - 1 do begin 383 409 PI.Y := 0; 384 for Y := 0 to Size.Y - 1 do385 for X := 0 to Size.X - 1 do begin386 Cells[Y, X].NewValue := Cells[Y, X].Value;387 Cells[Y, X].Moving := False;410 for Y := 0 to Board.Size.Y - 1 do 411 for X := 0 to Board.Size.X - 1 do begin 412 Board.Tiles[Y, X].NewValue := Board.Tiles[Y, X].Value; 413 Board.Tiles[Y, X].Moving := False; 388 414 end; 389 415 … … 395 421 PNew.Y := P.Y + DirectionDiff[Direction].Y; 396 422 if IsValidPos(PNew) then begin 397 if ( Cells[P.Y, P.X].NewValue <> 0) then begin398 if ( Cells[PNew.Y, PNew.X].NewValue = 0) then begin399 Cells[P.Y, P.X].Moving := True;400 Cells[PNew.Y, PNew.X].NewValue := Cells[P.Y, P.X].NewValue;401 Cells[PNew.Y, PNew.X].Merged := Cells[P.Y, P.X].Merged;402 Cells[P.Y, P.X].NewValue := 0;403 Cells[P.Y, P.X].Merged := False;423 if (Board.Tiles[P.Y, P.X].NewValue <> 0) then begin 424 if (Board.Tiles[PNew.Y, PNew.X].NewValue = 0) then begin 425 Board.Tiles[P.Y, P.X].Moving := True; 426 Board.Tiles[PNew.Y, PNew.X].NewValue := Board.Tiles[P.Y, P.X].NewValue; 427 Board.Tiles[PNew.Y, PNew.X].Merged := Board.Tiles[P.Y, P.X].Merged; 428 Board.Tiles[P.Y, P.X].NewValue := 0; 429 Board.Tiles[P.Y, P.X].Merged := False; 404 430 Inc(MovedCount); 405 431 end else 406 if (not Cells[P.Y, P.X].Merged) and (not Cells[PNew.Y, PNew.X].Merged) and407 ( Cells[PNew.Y, PNew.X].NewValue = Cells[P.Y, P.X].NewValue) then begin408 Cells[P.Y, P.X].Moving := True;409 Cells[PNew.Y, PNew.X].NewValue := Cells[PNew.Y, PNew.X].NewValue + Cells[P.Y, P.X].NewValue;410 Cells[PNew.Y, PNew.X].Merged := True;411 Cells[P.Y, P.X].NewValue := 0;412 Cells[P.Y, P.X].Merged := False;432 if (not Board.Tiles[P.Y, P.X].Merged) and (not Board.Tiles[PNew.Y, PNew.X].Merged) and 433 (Board.Tiles[PNew.Y, PNew.X].NewValue = Board.Tiles[P.Y, P.X].NewValue) then begin 434 Board.Tiles[P.Y, P.X].Moving := True; 435 Board.Tiles[PNew.Y, PNew.X].NewValue := Board.Tiles[PNew.Y, PNew.X].NewValue + Board.Tiles[P.Y, P.X].NewValue; 436 Board.Tiles[PNew.Y, PNew.X].Merged := True; 437 Board.Tiles[P.Y, P.X].NewValue := 0; 438 Board.Tiles[P.Y, P.X].Merged := False; 413 439 Inc(MovedCount); 414 Score := Score + Cells[PNew.Y, PNew.X].NewValue;440 Score := Score + Board.Tiles[PNew.Y, PNew.X].NewValue; 415 441 end; 416 442 end; … … 425 451 end; 426 452 427 // Animate cellmove453 // Animate tiles move 428 454 StartTime := Now; 429 EndTime := StartTime + AnimationDuration / 300 * OneSecond / Size.X;455 EndTime := StartTime + AnimationDuration / 300 * OneSecond / Max(Board.Size.X, Board.Size.Y); 430 456 if AnimationDuration > 0 then 431 457 repeat … … 433 459 Part := (Time - StartTime) / (EndTime - StartTime); 434 460 if Part > 1 then Part := 1; 435 for Y := 0 to Size.Y - 1 do436 for X := 0 to Size.X - 1 do begin437 if Cells[Y, X].Moving then438 Cells[Y, X].Shift := Point(Trunc(Part * MoveDirection.X * 100),461 for Y := 0 to Board.Size.Y - 1 do 462 for X := 0 to Board.Size.X - 1 do begin 463 if Board.Tiles[Y, X].Moving then 464 Board.Tiles[Y, X].Shift := Point(Trunc(Part * MoveDirection.X * 100), 439 465 Trunc(Part * MoveDirection.Y * 100)); 440 466 end; … … 444 470 until Time > EndTime; 445 471 446 // Set final cellvalues447 for Y := 0 to Size.Y - 1 do448 for X := 0 to Size.X - 1 do begin449 Cells[Y, X].Shift := Point(0, 0);450 Cells[Y, X].Moving := False;451 Cells[Y, X].Value := Cells[Y, X].NewValue;472 // Set final tiles values 473 for Y := 0 to Board.Size.Y - 1 do 474 for X := 0 to Board.Size.X - 1 do begin 475 Board.Tiles[Y, X].Shift := Point(0, 0); 476 Board.Tiles[Y, X].Moving := False; 477 Board.Tiles[Y, X].Value := Board.Tiles[Y, X].NewValue; 452 478 end; 453 479 DoChange; … … 457 483 end; 458 484 459 procedure TGame.Move Cell(SourceCell, TargetCell: TCell);460 begin 461 Target Cell.Value := SourceCell.Value;462 Source Cell.Value := 0;463 Target Cell.Merged := SourceCell.Merged;464 Source Cell.Merged := False;485 procedure TGame.MoveTile(SourceTile, TargetTile: TTile); 486 begin 487 TargetTile.Value := SourceTile.Value; 488 SourceTile.Value := 0; 489 TargetTile.Merged := SourceTile.Merged; 490 SourceTile.Merged := False; 465 491 end; 466 492 467 493 function TGame.IsValidPos(Pos: TPoint): Boolean; 468 494 begin 469 Result := (Pos.X >= 0) and (Pos.X < FSize.X) and470 (Pos.Y >= 0) and (Pos.Y < FSize.Y);495 Result := (Pos.X >= 0) and (Pos.X < Board.Size.X) and 496 (Pos.Y >= 0) and (Pos.Y < Board.Size.Y); 471 497 end; 472 498 … … 474 500 begin 475 501 AnimationDuration := 30; 502 Board := TBoard.Create; 476 503 end; 477 504 478 505 destructor TGame.Destroy; 479 506 begin 480 Size := Point(0, 0);507 FreeAndNil(Board); 481 508 inherited; 482 509 end; 483 510 484 function TGame.Get CellColor(Value: Integer): TColor;511 function TGame.GetTileColor(Value: Integer): TColor; 485 512 begin 486 513 case Value of
Note:
See TracChangeset
for help on using the changeset viewer.