Subversion Repositories jumper

Compare Revisions

Regard whitespace Rev 10 → Rev 11

/trunk/Boards/Asymetric 3-3-2-2 Board with diagonal moves.brd
1,9 → 1,10
1D~
XX222XXX!
XX222XXX
XX222XXX
22222222
222>E2222
22222222
XX222XXX
XX222XXX
Version 2
Mode: Diagonal
* * y y y * * *
* * y y y * * *
* * y y y * * *
y y y y y y y y
y y y E y y y y
y y y y y y y y
* * y y y * * *
* * y y y * * *
/trunk/Boards/Asymetric 3-3-2-2 Board.brd
1,9 → 1,10
1N~
XX222XXX!
XX222XXX
XX222XXX
22222222
222>E2222
22222222
XX222XXX
XX222XXX
Version 2
Mode: Normal
* * y y y * * *
* * y y y * * *
* * y y y * * *
y y y y y y y y
y y y E y y y y
y y y y y y y y
* * y y y * * *
* * y y y * * *
/trunk/Boards/Diamond Board with diagonal moves.brd
1,10 → 1,11
1D~
XXXX2XXXX!
XXX222XXX
XX22222XX
X2222222X
2222>E2222
X2222222X
XX22222XX
XXX222XXX
XXXX2XXXX
Version 2
Mode: Diagonal
* * * * y * * * *
* * * y y y * * *
* * y y y y y * *
* y y y y y y y *
y y y y E y y y y
* y y y y y y y *
* * y y y y y * *
* * * y y y * * *
* * * * y * * * *
/trunk/Boards/Diamond Board.brd
1,10 → 1,11
1N~
XXXX2XXXX!
XXX222XXX
XX22222XX
X2222222X
2222>E2222
X2222222X
XX22222XX
XXX222XXX
XXXX2XXXX
Version 2
Mode: Normal
* * * * y * * * *
* * * y y y * * *
* * y y y y y * *
* y y y y y y y *
y y y y E y y y y
* y y y y y y y *
* * y y y y y * *
* * * y y y * * *
* * * * y * * * *
/trunk/Boards/Drag-n-Drop Board Variant A.brd
1,8 → 1,9
1N~
XX333XX!
X32223X
3211123
321E123
3211123
X32223X
XX333XX
Version 2
Mode: Normal
* * r r r * *
* r y y y r *
r y g g g y r
r y g e g y r
r y g g g y r
* r y y y r *
* * r r r * *
/trunk/Boards/Drag-n-Drop Board Variant A with diagonal moves.brd
1,8 → 1,9
1D~
XX333XX!
X32223X
3211123
321E123
3211123
X32223X
XX333XX
Version 2
Mode: Diagonal
* * r r r * *
* r y y y r *
r y g g g y r
r y g e g y r
r y g g g y r
* r y y y r *
* * r r r * *
/trunk/Boards/Drag-n-Drop Board Variant B with diagonal moves.brd
1,8 → 1,9
1D~
XXE33XX!
X32223X
3211123
3211123
3211123
X32223X
XX333XX
Version 2
Mode: Diagonal
* * e r r * *
* r y y y r *
r y g g g y r
r y g g g y r
r y g g g y r
* r y y y r *
* * r r r * *
/trunk/Boards/Drag-n-Drop Board Variant B.brd
1,8 → 1,9
1N~
XXE33XX!
X32223X
3211123
3211123
3211123
X32223X
XX333XX
Version 2
Mode: Normal
* * e r r * *
* r y y y r *
r y g g g y r
r y g g g y r
r y g g g y r
* r y y y r *
* * r r r * *
/trunk/Boards/English Standard Board with diagonal moves.brd
1,8 → 1,9
1D~
XX222XX!
XX222XX
2222222
222>E222
2222222
XX222XX
XX222XX
Version 2
Mode: Diagonal
* * y y y * *
* * y y y * *
y y y y y y y
y y y E y y y
y y y y y y y
* * y y y * *
* * y y y * *
/trunk/Boards/English Standard Board.brd
1,8 → 1,9
1N~
XX222XX!
XX222XX
2222222
222>E222
2222222
XX222XX
XX222XX
Version 2
Mode: Normal
* * y y y * *
* * y y y * *
y y y y y y y
y y y E y y y
y y y y y y y
* * y y y * *
* * y y y * *
/trunk/Boards/French Solitaire with diagonal moves.brd
1,8 → 1,9
1D~
XX222XX!
X22222X
222E222
2222222
222>2222
X22222X
XX222XX
Version 2
Mode: Diagonal
* * y y y * *
* y y y y y *
y y y e y y y
y y y y y y y
y y y Y y y y
* y y y y y *
* * y y y * *
/trunk/Boards/French Solitaire.brd
1,8 → 1,9
1N~
XX222XX!
X22222X
222E222
2222222
222>2222
X22222X
XX222XX
Version 2
Mode: Normal
* * y y y * *
* y y y y y *
y y y e y y y
y y y y y y y
y y y Y y y y
* y y y y y *
* * y y y * *
/trunk/Boards/FunCircus with diagonal moves.brd
1,10 → 1,11
1D~
213231232!
123123123
123122313
123131223
1232E3213
123213122
112312312
122321232
123232132
Version 2
Mode: Diagonal
y g r y r g y r y
g y r g y r g y r
g y r g y y r g r
g y r g r g y y r
g y r y e r y g r
g y r y g r g y y
g g y r g y r g y
g y y r y g y r y
g y r y r y g r y
/trunk/Boards/FunCircus.brd
1,10 → 1,11
1N~
213231232!
123123123
123122313
123131223
1232E3213
123213122
112312312
122321232
123232132
Version 2
Mode: Normal
y g r y r g y r y
g y r g y r g y r
g y r g y y r g r
g y r g r g y y r
g y r y e r y g r
g y r y g r g y y
g g y r g y r g y
g y y r y g y r y
g y r y r y g r y
/trunk/Boards/German Wiegleb Board with diagonal moves.brd
1,10 → 1,11
1D~
XXX222XXX!
XXX222XXX
XXX222XXX
222222222
2222>E2222
222222222
XXX222XXX
XXX222XXX
XXX222XXX
Version 2
Mode: Diagonal
* * * y y y * * *
* * * y y y * * *
* * * y y y * * *
y y y y y y y y y
y y y y E y y y y
y y y y y y y y y
* * * y y y * * *
* * * y y y * * *
* * * y y y * * *
/trunk/Boards/German Wiegleb Board.brd
1,10 → 1,11
1N~
XXX222XXX!
XXX222XXX
XXX222XXX
222222222
2222>E2222
222222222
XXX222XXX
XXX222XXX
XXX222XXX
Version 2
Mode: Normal
* * * y y y * * *
* * * y y y * * *
* * * y y y * * *
y y y y y y y y y
y y y y E y y y y
y y y y y y y y y
* * * y y y * * *
* * * y y y * * *
* * * y y y * * *
/trunk/Boards/How to develop levels.txt
3,35 → 3,24
 
Path and name: Boards\*.brd
Author: Daniel Marschall
Revision: March, 7th 2009
Revision: 11 November 2018
 
== Header sequence ==
 
1 Version/Variant 1
D|N Are diagonal moves allowed or not allowed?
~ End of header
line 1: version 2
line 2: mode: normal
or
mode: diagonal
 
== The elements ==
 
X This is a locked field. You cannot place a stone there
3 This is a space with a red (30) stone.
2 This is a space with a yellow (20) stone.
1 This is a space with a green (10) stone.
E This is a space.
* This is handled like a locked field (X), but it has only the half width.
This is necessary if you want to have a playground in a triangle shape.
* This is a locked field. You cannot place a stone there.
. This is a locked field with the width of a half block. Use it for aligning triangle shaped boards.
r Slot with a red (30 pts) stone.
y Slot with a yellow (20 pts) stone.
g Slot filled with a green (10 pts) stone.
e Empty slot
 
== Special chars between the elements ==
Uppercase means the slot is a target (where the last stone has to go).
 
> The next element (which may not be X, * or a line end) is the area,
where the last stone has to go to.
! Between the first and the second line of the matrix, there has to
be an "!". This is neccessary that the width can be detected.
There has to be one "!" in the file. Also, the level has to be complete.
If there are 9 elements before "!", then the level is only valid,
if there are 0, 9, 18, 27, 36, ... elements after the "!".
 
== Hints ==
 
Carriage-Return (CR) and Line-feeds (LF) are optional, but they are recommended because
of a better overview.
Whitespaces and extra line breaks are ignored.
/trunk/Boards/Triangle Board Variant A.brd
1,6 → 1,7
1D~
****E!
***22
**222
*2222
22>222
Version 2
Mode: Diagonal
....e
...y y
..y y y
.y y y y
y y Y y y
/trunk/Boards/Triangle Board Variant B.brd
1,6 → 1,7
1D~
****>E!
***22
**222
*2222
22222
Version 2
Mode: Diagonal
....E
...y y
..y y y
.y y y y
y y y y y
/trunk/Choice.dfm
2,7 → 2,7
Left = 257
Top = 146
Caption = 'ViaThinkSoft Peg Solitaire - Board choice'
ClientHeight = 406
ClientHeight = 415
ClientWidth = 656
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
82,7 → 82,7
Left = 24
Top = 32
Bitmap = {
494C010103001800200020002000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
494C010103001800340020002000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
0000000000003600000028000000800000002000000001002000000000000040
0000000000000000000000000000000000000000000000000000000000000000
0000800000008000000080000000800000008000000080000000800000008000
/trunk/Choice.pas
50,19 → 50,22
 
procedure TLevelChoice.PlayBtnClick(Sender: TObject);
var
LevelString: string;
Level: TLevel;
begin
if Assigned(LevelList.Selected) then
begin
if LevelList.Selected.ImageIndex = 2 then
begin
LevelString := Functions.ReadFile(Format(LVL_FILE, [LevelList.Selected.Caption]));
// leNone könnte auftreten, wenn das Level z.B. repariert wurde...
if CheckLevelIntegrity(LevelString, true) <> leNone then
Level := TLevel.Create(Format(LVL_FILE, [LevelList.Selected.Caption]));
try
if Level.CheckLevelIntegrity(true) <> leNone then
begin
exit;
end;
finally
FreeAndNil(Level);
end;
end;
ModalResult := mrOk;
end;
end;
80,6 → 83,7
procedure TLevelChoice.LevelListClick(Sender: TObject);
var
LevelFile, LevelString: string;
Level: TLevel;
begin
PlayBtn.Enabled := Assigned(LevelList.Selected);
PLoadLevel.Enabled := Assigned(LevelList.Selected);
87,8 → 91,12
if Assigned(LevelList.Selected) then
begin
LevelFile := Format(LVL_FILE, [LevelList.Selected.Caption]);
LevelString := Functions.ReadFile(LevelFile);
DrawLevelPreview(LevelString, PreviewImage, Color);
Level := TLevel.Create(LevelFile);
try
DrawLevelPreview(Level, PreviewImage, Color);
finally
FreeAndNil(Level);
end;
end
else
begin
110,7 → 118,7
procedure TLevelChoice.RefreshList;
var
s: TSearchRec;
LevelString: string;
Level: TLevel;
begin
LevelList.Clear;
 
121,11 → 129,11
with LevelList.Items.Add do
begin
Caption := Copy(s.Name, 1, Length(s.Name)-Length(LVL_EXT));
LevelString := Functions.ReadFile(LVL_PATH + s.Name);
case GetLevelType(LevelString) of
ltStandard: ImageIndex := 0;
ltDiagonal: ImageIndex := 1;
ltError: ImageIndex := 2;
Level := TLevel.Create(LVL_PATH + s.Name);
case Level.GetGameMode of
gmNormal: ImageIndex := 0;
gmDiagonal: ImageIndex := 1;
gmUndefined: ImageIndex := 2;
end;
end;
until FindNext(s) <> 0;
/trunk/Functions.pas
12,7 → 12,6
function Explode(Separator, Text: String): TStringList;
function Position(FullString, Search: String): Integer;
function ReadFile(InputFile: string): string;
function RemoveLineBreaks(inp: string): string;
 
implementation
 
129,12 → 128,5
CloseFile(f);
end;
 
function RemoveLineBreaks(inp: string): string;
begin
inp := StringReplace(inp, #13, '', [rfReplaceAll]);
inp := StringReplace(inp, #10, '', [rfReplaceAll]);
result := inp;
end;
 
end.
/trunk/HighScore.dfm
26,6 → 26,10
object PPMTab: TTabSheet
Caption = 'Highest Points per Minute'
ImageIndex = 3
ExplicitLeft = 0
ExplicitTop = 0
ExplicitWidth = 0
ExplicitHeight = 0
object PPMList: TListView
Left = 8
Top = 8
55,6 → 59,10
end
object RemainingTab: TTabSheet
Caption = 'Lowest remaining stones'
ExplicitLeft = 0
ExplicitTop = 0
ExplicitWidth = 0
ExplicitHeight = 0
object RemainingList: TListView
Left = 8
Top = 8
85,6 → 93,10
object TimeTab: TTabSheet
Caption = 'Shortest time'
ImageIndex = 1
ExplicitLeft = 0
ExplicitTop = 0
ExplicitWidth = 0
ExplicitHeight = 0
object TimeList: TListView
Left = 8
Top = 8
115,6 → 127,10
object ScoreTab: TTabSheet
Caption = 'Highest score'
ImageIndex = 2
ExplicitLeft = 0
ExplicitTop = 0
ExplicitWidth = 0
ExplicitHeight = 0
object ScoreList: TListView
Left = 8
Top = 8
/trunk/LevelFunctions.pas
3,10 → 3,10
interface
 
uses
SysUtils, Dialogs, Functions, ExtCtrls, Graphics;
SysUtils, Dialogs, Functions, ExtCtrls, Graphics, Classes, Math;
 
type
TFieldType = (ftLocked, ftLockedWithTab, ftEmpty, ftGreen, ftYellow, ftRed);
TFieldType = (ftFullSpace, ftHalfSpace, ftEmpty, ftRed, ftYellow, ftGreen);
 
TFieldProperties = record
Typ: TFieldType;
13,71 → 13,57
Goal: Boolean;
end;
 
TLevelType = (ltStandard, ltDiagonal, ltError);
TGameMode = (gmUndefined, gmNormal, gmDiagonal);
 
TLevelArray = array of array of TFieldProperties;
 
TLevelError = (leNone, leInvalidElement, leNoIndicator, leMultipleIndicator,
leLevelIncomplete, leHeaderError, leInvalidGoal);
TLevelError = (leNone, leInvalidElement, leEmptyBoard, leRowInvalidLength,
leUnsupportedVersion, leUnsupportedMode);
 
procedure DrawLevelPreview(LevelString: string; Image: TImage; BackgroundColor: TColor);
function GetLevelType(LevelString: string): TLevelType;
function CheckLevelIntegrity(LevelString: string; ShowErrors: boolean): TLevelError; overload;
function CheckLevelIntegrity(LevelString: string): TLevelError; overload;
function LevelStringToLevelArray(LevelString: string; ShowErrors: boolean): TLevelArray;
TLevel = class(TObject)
private
FStringList: TStringList;
procedure Load(ABoardFile: string);
public
constructor Create(ABoardFile: string);
destructor Destroy; override;
function LevelStringToLevelArray(ShowErrors: boolean): TLevelArray;
function CheckLevelIntegrity: TLevelError; overload;
function CheckLevelIntegrity(ShowErrors: boolean): TLevelError; overload;
function GetGameMode: TGameMode;
end;
 
procedure DrawLevelPreview(Level: TLevel; Image: TImage; BackgroundColor: TColor);
function FieldTypeWorth(t: TFieldType): integer;
 
var
AllowDiagonalMoves: boolean;
 
implementation
 
const
PREVIEW_BLOCK_SIZE = 10; // Enthält Field und Abstand
PREVIEW_TAB_SIZE = PREVIEW_BLOCK_SIZE div 2; // 5
HEADER_SIZE = 3;
ERED = '3';
EYEL = '2';
EGRE = '1';
ELOC = 'X';
EEMP = 'E';
EIND = '!';
ETAR = '>';
ESPE = '*';
TY_DIA = 'D';
TY_NOR = 'N';
 
resourcestring
LNG_LVL_INVALID_ELEMENT = 'Level invalid: There are invalid elements in the file.'+#13#10#13#10+'Valid elements are "1", "2", "3", "X", "*", "E" and ">" as goal prefix.';
LNG_LVL_INVALID_WIDTH = 'Level invalid: No width indicator ("!") found.';
LNG_LVL_INVALID_HEIGHT_MUTLIPLE = 'Level invalid: The level''s actual length is not a multiple of the width.';
LNG_LVL_INVALID_MULTIPLE_WIND = 'Level invalid: There are multiple width indicators ("!").';
LNG_LVL_INVALID_HEADER = 'Level invalid: The header is invalid. It does not match the structure "1(D|N)~".';
LNG_INVALID_GOAL = 'Level invalid: A goal does not point to a valid accessable element ("3", "2", "1" or "E").';
 
procedure DrawLevelPreview(LevelString: string; Image: TImage; BackgroundColor: TColor);
procedure DrawLevelPreview(Level: TLevel; Image: TImage; BackgroundColor: TColor);
var
LevelArray: TLevelArray;
i, j: integer;
y, x: integer;
t: TFieldType;
halftabs: integer;
const
PREVIEW_BLOCK_SIZE = 10; // Enthält Field und Abstand
PREVIEW_TAB_SIZE = PREVIEW_BLOCK_SIZE div 2; // 5
begin
LevelArray := nil;
 
ClearImage(Image, BackgroundColor);
 
LevelArray := LevelStringToLevelArray(LevelString, false);
LevelArray := Level.LevelStringToLevelArray(false);
 
for i := Low(LevelArray) to High(LevelArray) do
for y := Low(LevelArray) to High(LevelArray) do
begin
halftabs := 0;
for j := Low(LevelArray[i]) to High(LevelArray[i]) do
for x := Low(LevelArray[y]) to High(LevelArray[y]) do
begin
t := LevelArray[i][j].Typ;
t := LevelArray[y][x].Typ;
 
case t of
ftLocked: Image.Canvas.Brush.Color := BackgroundColor;
ftLockedWithTab: begin
ftFullSpace: Image.Canvas.Brush.Color := BackgroundColor;
ftHalfSpace: begin
Image.Canvas.Brush.Color := BackgroundColor;
inc(halftabs);
end;
87,231 → 73,215
ftRed: Image.Canvas.Brush.Color := clRed;
end;
 
if LevelArray[i][j].Goal then
if LevelArray[y][x].Goal then
Image.Canvas.Pen.Color := clBlack
else
Image.Canvas.Pen.Color := BackgroundColor;
 
Image.Canvas.Rectangle(j*PREVIEW_BLOCK_SIZE - halftabs*PREVIEW_TAB_SIZE, i*PREVIEW_BLOCK_SIZE, j*PREVIEW_BLOCK_SIZE - halftabs*PREVIEW_TAB_SIZE+PREVIEW_BLOCK_SIZE, i*PREVIEW_BLOCK_SIZE+PREVIEW_BLOCK_SIZE);
Image.Canvas.Rectangle((x-halftabs)*PREVIEW_BLOCK_SIZE + halftabs*PREVIEW_TAB_SIZE,
y*PREVIEW_BLOCK_SIZE,
(x-halftabs)*PREVIEW_BLOCK_SIZE + halftabs*PREVIEW_TAB_SIZE + PREVIEW_BLOCK_SIZE,
y*PREVIEW_BLOCK_SIZE + PREVIEW_BLOCK_SIZE);
end;
end;
end;
 
function GetLevelType(LevelString: string): TLevelType;
function FieldTypeWorth(t: TFieldType): integer;
begin
if CheckLevelIntegrity(LevelString) = leNone then
begin
if Copy(LevelString, 2, 1) = TY_DIA then
begin
result := ltDiagonal;
end
else // if Copy(LevelString, 2, 1) = TY_NOR
begin
result := ltStandard;
if t = ftGreen then result := 10
else if t = ftYellow then result := 20
else if t = ftRed then result := 30
else result := 0;
end;
end
else
 
{ TLevel }
 
const NUM_HEADERS = 2;
 
constructor TLevel.Create(ABoardFile: string);
begin
result := ltError;
inherited Create;
FStringList := TStringList.Create;
Load(ABoardFile);
end;
end;
 
procedure ShowErrorMessage(error: TLevelError);
destructor TLevel.Destroy;
begin
case error of
leNone: ;
leInvalidElement: MessageDlg(LNG_LVL_INVALID_ELEMENT, mtError, [mbOk], 0);
leNoIndicator: MessageDlg(LNG_LVL_INVALID_WIDTH, mtError, [mbOk], 0);
leMultipleIndicator: MessageDlg(LNG_LVL_INVALID_MULTIPLE_WIND, mtError, [mbOk], 0);
leLevelIncomplete: MessageDlg(LNG_LVL_INVALID_HEIGHT_MUTLIPLE, mtError, [mbOk], 0);
leHeaderError: MessageDlg(LNG_LVL_INVALID_HEADER, mtError, [mbOk], 0);
leInvalidGoal: MessageDlg(LNG_INVALID_GOAL, mtError, [mbOk], 0);
FreeAndNil(FStringList);
 
inherited;
end;
end;
 
function CheckLevelIntegrity(LevelString: string; ShowErrors: boolean): TLevelError;
function TLevel.GetGameMode: TGameMode;
begin
result := CheckLevelIntegrity(LevelString);
if ShowErrors then ShowErrorMessage(result);
if LowerCase(FStringList.Strings[1]) = 'mode: normal' then
result := gmNormal
else if LowerCase(FStringList.Strings[1]) = 'mode: diagonal' then
result := gmDiagonal
else
result := gmUndefined;
end;
 
function CheckLevelIntegrity(LevelString: string): TLevelError;
procedure TLevel.Load(ABoardFile: string);
var
W: integer;
H: extended;
header, h_ver, h_dia, h_del, tmp: string;
p: integer;
i: Integer;
begin
result := leNone;
FStringList.Clear;
FStringList.LoadFromFile(ABoardFile);
 
// Entfernt die Zeilenumbrüche
 
LevelString := RemoveLineBreaks(LevelString);
 
// Check 1: Ist der Header OK?
 
header := copy(LevelString, 1, HEADER_SIZE);
 
h_ver := copy(header, 1, 1);
if h_ver <> '1' then
// Remove whitespaces and empty lines
for i := FStringList.Count-1 downto NUM_HEADERS do
begin
result := leHeaderError;
Exit;
FStringList.Strings[i] := StringReplace(FStringList.Strings[i], ' ', '', [rfReplaceAll]);
if FStringList.Strings[i] = '' then FStringList.Delete(i);
end;
end;
 
h_dia := copy(header, 2, 1);
if (h_dia <> TY_DIA) and (h_dia <> TY_NOR) then
function TLevel.LevelStringToLevelArray(ShowErrors: boolean): TLevelArray;
var
i: integer;
t: TFieldType;
err: TLevelError;
longestLine: Integer;
thisLine: Integer;
y: Integer;
x: Integer;
Line: string;
lch, uch: char;
ch: char;
begin
result := leHeaderError;
Exit;
end;
// Zuerst nach Fehlern suchen
err := CheckLevelIntegrity(ShowErrors);
if err <> leNone then exit;
 
h_del := copy(header, 3, 1);
if h_del <> '~' then
// Längste Zeile finden
longestLine := 0;
for i := NUM_HEADERS to FStringList.Count-1 do
begin
result := leHeaderError;
Exit;
longestLine := Max(longestLine, Length(FStringList.Strings[i]));
end;
 
LevelString := copy(LevelString, HEADER_SIZE+1, Length(LevelString)-HEADER_SIZE);
// Nun Matrix aufbauen
SetLength(result, 0);
for i := NUM_HEADERS to FStringList.Count-1 do
begin
y := i - NUM_HEADERS;
 
// Check 2: Steht das ggf. vorhandenen ">" vor einem gültigen Feld 1, 2, 3, E?
SetLength(result, Length(result)+1); // add line to matrix
SetLength(result[y], longestLine);
 
p := Position(LevelString, ETAR);
Line := FStringList.Strings[i];
 
while (p <> -1) do
for x := 0 to LongestLine-1 do
begin
tmp := copy(LevelString, p+1, 1);
ch := Copy(Line,x+1,1)[1];
lch := LowerCase(ch)[1];
uch := UpperCase(ch)[1];
case lch of
'*': t := ftFullSpace;
'.': t := ftHalfSpace;
'e': t := ftEmpty;
'r': t := ftRed;
'y': t := ftYellow;
'g': t := ftGreen;
end;
 
if (tmp <> EEMP) and (tmp <> EGRE) and (tmp <> EYEL) and (tmp <> ERED) then
begin
result := leInvalidGoal;
Exit;
result[y][x].Typ := t;
result[y][x].Goal := (ch = uch) and (ch <> lch);
end;
end;
end;
 
LevelString := StringReplace(LevelString, ETAR, '', []); // Dieses Ziel entfernen
 
p := Position(LevelString, ETAR);
function TLevel.CheckLevelIntegrity(ShowErrors: boolean): TLevelError;
resourcestring
LNG_LVL_INVALID_ELEMENT = 'Level invalid: There are invalid elements in the file.'+#13#10#13#10+'Valid elements are r/R, y/Y, g/G, e/E, . and *.';
LNG_LVL_UNSUPPORTED_VERSION = 'Level format invalid: Version not supported.';
LNG_LVL_UNSUPPORTED_MODE = 'Level format invalid: Mode not supported.';
LNG_LVL_EMPTY_BOARD = 'Level invalid: Board is empty.';
LNG_LVL_INVALID_LENGTH = 'Level invalid: Lines don''t have an equal amount of elements.';
begin
result := CheckLevelIntegrity;
if ShowErrors then
begin
case result of
leNone: ;
leInvalidElement: MessageDlg(LNG_LVL_INVALID_ELEMENT, mtError, [mbOk], 0);
leUnsupportedVersion: MessageDlg(LNG_LVL_UNSUPPORTED_VERSION, mtError, [mbOk], 0);
leUnsupportedMode: MessageDlg(LNG_LVL_UNSUPPORTED_MODE, mtError, [mbOk], 0);
leEmptyBoard: MessageDlg(LNG_LVL_EMPTY_BOARD, mtError, [mbOk], 0);
leRowInvalidLength: MessageDlg(LNG_LVL_INVALID_LENGTH, mtError, [mbOk], 0);
end;
end;
end;
 
// Check 3: Kommt überhaupt ein "!" vor?
function TLevel.CheckLevelIntegrity: TLevelError;
var
W: integer;
H: extended;
header, h_ver, h_dia, h_del, tmp: string;
p: integer;
i: Integer;
Line: string;
begin
result := leNone;
 
W := Position(LevelString, EIND);
// Check 1: Ist der Header OK?
 
if W = -1 then
if LowerCase(FStringList.Strings[0]) <> 'version 2' then
begin
result := leNoIndicator;
Exit;
result := leUnsupportedVersion;
exit;
end;
 
// Check 4: Kam das "!" mehrmals vor?
 
LevelString := StringReplace(LevelString, EIND, '', []); // Das Erste entfernen
 
if Position(LevelString, EIND) <> -1 then // gibt es ein Zweites?
if ((LowerCase(FStringList.Strings[1]) <> 'mode: normal') and (LowerCase(FStringList.Strings[1]) <> 'mode: diagonal')) then
begin
result := leMultipleIndicator;
Exit;
result := leUnsupportedMode;
exit;
end;
 
// Check 5: Geht das Level nicht in einem Quadrat oder Rechteck auf?
// Check 2: Ist das Brett leer?
 
H := (Length(LevelString) - 1) / W;
 
if not Ganzzahlig(H) then
if FStringList.Count - NUM_HEADERS = 0 then
begin
result := leLevelIncomplete;
Exit;
result := leEmptyBoard;
exit;
end;
 
// Check 6: Gibt es ungültige Elemente im LevelString?
// Check 3: Geht das Level nicht in einem Quadrat oder Rechteck auf?
 
LevelString := StringReplace(LevelString, ESPE, '', [rfReplaceAll]);
LevelString := StringReplace(LevelString, ELOC, '', [rfReplaceAll]);
LevelString := StringReplace(LevelString, EEMP, '', [rfReplaceAll]);
LevelString := StringReplace(LevelString, EGRE, '', [rfReplaceAll]);
LevelString := StringReplace(LevelString, EYEL, '', [rfReplaceAll]);
LevelString := StringReplace(LevelString, ERED, '', [rfReplaceAll]);
 
if Length(LevelString) > 0 then
for i := NUM_HEADERS to FStringList.Count-1 do
begin
result := leInvalidElement;
Exit;
if Length(FStringList.Strings[i]) <> Length(FStringList.Strings[NUM_HEADERS]) then
begin
result := leRowInvalidLength; // at row y-NUM_HEADERS
exit;
end;
end;
 
// Check 7: Kann im Level gesprungen werden
// Check 4: Gibt es ungültige Elemente in den Zeilen?
 
{ Wird hier nicht abgeprüft, da dafür zuerst der PlayGround gebaut sein muss.
Es ist außerdem eher ein logischer Fehler, kein Fehler in der Levelstruktur! }
end;
 
function LevelStringToLevelArray(LevelString: string; ShowErrors: boolean): TLevelArray;
var
i, j, j_dec, c: integer;
m: string;
t: TFieldType;
W, H: integer;
err: TLevelError;
NextIsGoal: boolean;
for i := NUM_HEADERS to FStringList.Count-1 do
begin
// Zuerst nach Fehlern suchen
err := CheckLevelIntegrity(LevelString, ShowErrors);
if err <> leNone then exit;
Line := FStringList.Strings[i];
 
// Headerinformationen auslesen
AllowDiagonalMoves := copy(LevelString, 2, 1) = TY_DIA;
Line := StringReplace(Line, '.', '', [rfReplaceAll]);
Line := StringReplace(Line, '*', '', [rfReplaceAll]);
Line := StringReplace(Line, 'r', '', [rfReplaceAll, rfIgnoreCase]);
Line := StringReplace(Line, 'y', '', [rfReplaceAll, rfIgnoreCase]);
Line := StringReplace(Line, 'g', '', [rfReplaceAll, rfIgnoreCase]);
Line := StringReplace(Line, 'e', '', [rfReplaceAll, rfIgnoreCase]);
 
// Header entfernen
LevelString := copy(LevelString, HEADER_SIZE+1, Length(LevelString)-HEADER_SIZE);
 
// Entfernt die Zeilenumbrüche
LevelString := RemoveLineBreaks(LevelString);
 
// Dimensionen abmessen
W := Position(StringReplace(LevelString, ETAR, '', [rfReplaceAll]), EIND) - 1;
LevelString := StringReplace(LevelString, EIND, '', [rfReplaceAll]);
H := Length(LevelString) div W;
 
c := 1;
NextIsGoal := false;
 
SetLength(result, round(H));
for i := Low(result) to High(result) do
if Length(Line) > 0 then
begin
j_dec := 0;
SetLength(result[i], round(W));
for j := Low(result[i]) to High(result[i])+1 do // +1 wegen dem möglichen zusätzlichem ">"
begin
if (j = High(result[i])+1) and (j_dec = 0) then break;
m := Copy(LevelString, c, 1);
if m = ETAR then
begin
NextIsGoal := true;
inc(j_dec);
end
else
begin
if m = EEMP then t := ftEmpty
else if m = EGRE then t := ftGreen
else if m = EYEL then t := ftYellow
else if m = ERED then t := ftRed
else if m = ESPE then t := ftLockedWithTab
else t := ftLocked;
result[i][j-j_dec].Typ := t;
result[i][j-j_dec].Goal := NextIsGoal;
if NextIsGoal then NextIsGoal := false;
result := leInvalidElement; // at row y-NUM_HEADERS
Exit;
end;
inc(c);
end;
end;
end;
 
function FieldTypeWorth(t: TFieldType): integer;
begin
if t = ftGreen then result := 10
else if t = ftYellow then result := 20
else if t = ftRed then result := 30
else result := 0;
// Check 5: Kann im Level gesprungen werden
 
{ Wird hier nicht abgeprüft, da dafür zuerst der PlayGround gebaut sein muss.
Es ist außerdem eher ein logischer Fehler, kein Fehler in der Levelstruktur! }
end;
 
end.
/trunk/Main.pas
68,6 → 68,7
LevelTotalStones: Integer;
LevelRemovedStones: Integer;
JumpHistory: TStringList;
Level: TLevel;
procedure LoadSettings;
procedure SaveSettings;
procedure RestartLevel;
224,6 → 225,8
MUndo.Enabled := false;
 
SetLength(LookupFieldCoordinateArray, 0);
 
if Assigned(Level) then FreeAndNil(Level);
end;
 
procedure TMainForm.LoadPictureForType(FieldType: TFieldType; Picture: TPicture);
298,8 → 301,8
begin
result := fsError;
case t of
ftLocked: result := fsLocked;
ftLockedWithTab: result := fsLocked;
ftFullSpace: result := fsLocked;
ftHalfSpace: result := fsLocked;
ftEmpty: result := fsAvailable;
ftGreen: result := fsStone;
ftYellow: result := fsStone;
370,7 → 373,7
if MayJump(x, y, x, y+2) then exit;
if MayJump(x, y, x, y-2) then exit;
 
if AllowDiagonalMoves then
if Level.GetGameMode = gmDiagonal then
begin
if MayJump(x, y, x-2, y-2) then exit;
if MayJump(x, y, x+2, y-2) then exit;
414,7 → 417,7
JumpHistory.Add(Format(LNG_JUMP_LOG, [SourceTag+1, s.x+1, s.y+1, DestTag+1, d.x+1, d.y+1]));
 
{$REGION 'Stein entfernen und Punkte vergeben'}
if AllowDiagonalMoves then
if Level.GetGameMode = gmDiagonal then
begin
if (s.X-2 = d.X) and (s.Y-2 = d.Y) and (FieldState(s.X-1, s.Y-1) = fsStone) then RemoveStone(s.X-1, s.Y-1, true);
if (s.X-2 = d.X) and (s.Y+2 = d.Y) and (FieldState(s.X-1, s.Y+1) = fsStone) then RemoveStone(s.X-1, s.Y+1, true);
485,7 → 488,7
if FieldState(DestX, DestY) <> fsAvailable then exit;
 
// Check 2: Befindet sich ein Stein zwischen Source und Destination und ist der Abstand 2?
if AllowDiagonalMoves then
if Level.GetGameMode = gmDiagonal then
begin
if (SourceX-2 = DestX) and (SourceY-2 = DestY) and (FieldState(SourceX-1, SourceY-1) = fsStone) then result := true;
if (SourceX-2 = DestX) and (SourceY+2 = DestY) and (FieldState(SourceX-1, SourceY+1) = fsStone) then result := true;
525,7 → 528,7
newField: TField;
index: integer;
begin
if (t.Typ = ftLocked) or (t.Typ = ftLockedWithTab) then exit;
if (t.Typ = ftFullSpace) or (t.Typ = ftHalfSpace) then exit;
 
index := Length(LookupFieldCoordinateArray);
 
576,7 → 579,7
halftabs := 0;
for j := Low(LevelArray[i]) to High(LevelArray[i]) do
begin
if LevelArray[i][j].Typ = ftLockedWithTab then inc(halftabs);
if LevelArray[i][j].Typ = ftHalfSpace then inc(halftabs);
DrawField(j, i, LevelArray[i][j], halftabs);
end;
cur_x := High(LevelArray[i]) + 1;
629,13 → 632,12
 
procedure TMainForm.NewGame(Filename: string);
var
LevelString: String;
LevelArray: TLevelArray;
begin
DestroyLevel;
LevelFile := Filename;
LevelString := ReadFile(LevelFile);
LevelArray := LevelStringToLevelArray(LevelString, true);
Level := TLevel.Create(LevelFile);
LevelArray := Level.LevelStringToLevelArray(true);
if Length(LevelArray) = 0 then Exit;
BuildPlayground(LevelArray);
if not AreJumpsPossible then
712,6 → 714,7
 
procedure TMainForm.FormDestroy(Sender: TObject);
begin
DestroyLevel;
JumpHistory.Free;
end;
 
/trunk/Private/Konzept.txt
28,3 → 28,4
 
Fragen
- Sollte es möglich sein, dass es mehrere Ziele gibt? z.B. 2 Ziele für 2 Steine?
- Sollte "Triangle" ein eigener Gamemode sein? (Ist genau so wie Diagonal, aber anderes Icon)