735 lines
18 KiB
ObjectPascal

{ Puzzle! is a puzzle game using BGRABitmap
originally written in 2012 by lainz http://sourceforge.net/users/lainz
Thanks to circular and eny from the Lazarus forum for their contributions.
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
for more details.
You should have received a copy of the GNU Library General Public License
along with this library; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs,
BGRAVirtualScreen, BGRABitmap, BGRABitmapTypes, ugame, LCLType,
ExtCtrls{$IFDEF WINDOWS},
mmsystem{$ENDIF};
const
// controls
L1 = VK_LEFT;
R1 = VK_RIGHT;
U1 = VK_UP;
D1 = VK_DOWN;
L2 = VK_A;
R2 = VK_D;
U2 = VK_W;
D2 = VK_S;
type
TArrayOfString = array of string;
TGameKey = (gkLeft, gkUp, gkRight, gkDown);
PGameMap = ^TGameMap;
TGameMap = record
map, solve: string;
mapW: integer;
mapH: integer;
blockW: integer;
blockH: integer;
background: TBGRABitmap;
end;
var
Game: array [1..12] of TGameMap;
GameOver: TGameMap = (map: 'Game-Over'; solve: 'GameOver-'; mapW: 3;
mapH: 3; blockW: 85; blockH: 85; background: nil);
procedure DrawMap(var map: TGameMap; bitmap: TBGRABitmap; texture: TBGRABitmap);
function ClickMap(var map: TGameMap; pos: TPoint): boolean;
function KeyPressMap(var map: TGameMap; var Key: word): boolean;
function IsMapSolved(var map: TGameMap): boolean;
function CalcularPosibles(str: string; showDebug: boolean = False): TarrayOfString;
type
{ TForm1 }
TForm1 = class(TForm)
BGRAVirtualScreen1: TBGRAVirtualScreen;
Timer1: TTimer;
procedure BGRAVirtualScreen1Click(Sender: TObject);
procedure BGRAVirtualScreen1Redraw(Sender: TObject; Bitmap: TBGRABitmap);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: word; Shift: TShiftState);
procedure FormResize(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
Size: TRect;
map: PGameMap;
current: integer;
texture: TBGRABitmap;
procedure OpenMap(num: integer);
procedure CloseMap;
procedure ScaleBlocks;
procedure GenerateTexture;
procedure PlayBlockSound;
end;
var
Form1: TForm1;
implementation
uses BGRAGradients, ugraph;
{ DRAWMAP }
procedure DrawMap(var map: TGameMap; bitmap: TBGRABitmap; texture: TBGRABitmap);
var
r: TRect;
n, n1, n2: integer;
phong: TPhongShading;
tile3D, empty3D: TBGRABitmap;
h: integer;
begin
if (bitmap = nil) or (texture = nil) then
exit;
phong := TPhongShading.Create;
if map.background = nil then
begin
map.background := TBGRABitmap.Create(map.BlockW * map.mapW, map.BlockH * map.mapH);
empty3D := TBGRABitmap.Create(texture.Width, texture.Height, BGRABlack);
for n1 := 1 to (map.background.Width + texture.Width - 1) div texture.Width do
for n2 := 1 to (map.background.Height + texture.Height - 1) div texture.Height do
phong.Draw(map.background, empty3D, 0, (n1 - 1) * texture.Width,
(n2 - 1) * texture.Height, texture);
empty3D.Free;
end;
h := (map.blockW + map.blockH) div 16;
tile3D := CreateRectanglePreciseMap(map.BlockW, map.BlockH, h, []);
bitmap.PutImage(0, 0, map.background, dmDrawWithTransparency);
n1 := 0;
n2 := 0;
for n := 1 to length(map.map) do
begin
r.Left := map.blockW * n1;
r.Top := map.blockH * n2;
r.Right := r.Left + map.blockW;
r.Bottom := r.Top + map.blockH;
// begin to draw here //
if map.map[n] <> '0' then
begin
phong.Draw(bitmap, tile3D, h, r.left, r.top, texture);
if map.blockH > map.blockW then
bitmap.FontHeight := round(map.blockW * 0.75)
else
bitmap.FontHeight := round(map.blockH * 0.75);
bitmap.TextRect(Rect(r.Left + 10, r.Top + 10, r.Right, r.Bottom),
map.map[n], taCenter, tlCenter, BGRA(0, 0, 0, 175));
bitmap.TextRect(r, map.map[n], taCenter, tlCenter, BGRABlack);
end;
{
if map.map[n] = '0' then
begin
bitmap.FontHeight := map.blockH div 4;
if (n1 <> 0) then
bitmap.TextRect(r, '►', taLeftJustify, tlCenter, BGRA(192, 192, 192));
if (n1 <> map.mapW - 1) then
bitmap.TextRect(r, '◄', taRightJustify, tlCenter, BGRA(192, 192, 192));
if (n2 <> 0) then
bitmap.TextRect(r, '▼', taCenter, tlTop, BGRA(192, 192, 192));
if (n2 <> map.mapH - 1) then
bitmap.TextRect(r, '▲', taCenter, tlBottom, BGRA(192, 192, 192));
end;
}
// end to draw here //
if n1 = map.mapW - 1 then
begin
n1 := -1;
Inc(n2);
end;
Inc(n1);
end;
tile3D.Free;
phong.Free;
end;
function ClickMap(var map: TGameMap; pos: TPoint): boolean;
var
n, n1, n2: integer;
r: TRect;
begin
Result := False;
n1 := 0;
n2 := 0;
for n := 1 to length(map.map) do
begin
r.Left := map.blockW * n1;
r.Top := map.blockH * n2;
r.Right := r.Left + map.blockW;
r.Bottom := r.Top + map.blockH;
{ SCALING }
r.Left += Form1.BGRAVirtualScreen1.Left;
r.Top += Form1.BGRAVirtualScreen1.Top;
r.Right += Form1.BGRAVirtualScreen1.Left;
r.Bottom += Form1.BGRAVirtualScreen1.Top;
if (pos.x >= r.Left) and (pos.x <= r.Right) and (pos.y >= r.Top) and
(pos.y <= r.Bottom) then
begin
// vacio
if map.map[n] = '0' then
exit;
// el vacio esta a la izquierda
if (n1 <> 0) then
if map.map[n - 1] = '0' then
begin
map.map[n - 1] := map.map[n];
map.map[n] := '0';
end;
// el vacio esta a la derecha
if (n1 <> map.mapW - 1) then
if map.map[n + 1] = '0' then
begin
map.map[n + 1] := map.map[n];
map.map[n] := '0';
end;
// el vacio esta arriba
if map.map[n - map.mapW] = '0' then
begin
map.map[n - map.mapW] := map.map[n];
map.map[n] := '0';
end;
// el vacio esta abajo
if map.map[n + map.mapW] = '0' then
begin
map.map[n + map.mapW] := map.map[n];
map.map[n] := '0';
end;
Result := True;
exit;
end;
if n1 = map.mapW - 1 then
begin
n1 := -1;
Inc(n2);
end;
Inc(n1);
end;
end;
function KeyPressMap(var map: TGameMap; var Key: word): boolean;
function GetGameKey(var Key: word; invertLR, invertUD: boolean): TGameKey;
begin
if (Key = L1) or (Key = L2) then
Result := gkLeft;
if (Key = R1) or (Key = R2) then
Result := gkRight;
if (Key = U1) or (Key = U2) then
Result := gkUp;
if (Key = D1) or (Key = D2) then
Result := gkDown;
if invertLR then
case Result of
gkLeft: Result := gkRight;
gkRight: Result := gkLeft;
end;
if invertUD then
case Result of
gkUp: Result := gkDown;
gkDown: Result := gkUp;
end;
end;
var
n, n1, n2: integer;
begin
n1 := 0;
n2 := 0;
Result := False;
for n := 1 to length(map.map) do
begin
if map.map[n] = '0' then
begin
case GetGameKey(Key, True, True) of
gkLeft:
// el de la izquierda
if (n1 <> 0) then
begin
map.map[n] := map.map[n - 1];
map.map[n - 1] := '0';
Result := True;
Key := 0;
end;
gkRight:
// el de la derecha
if (n1 <> map.mapW - 1) then
begin
map.map[n] := map.map[n + 1];
map.map[n + 1] := '0';
Result := True;
Key := 0;
end;
gkUp:
// el de arriba
if (n2 <> 0) then
begin
map.map[n] := map.map[n - map.mapW];
map.map[n - map.mapW] := '0';
Result := True;
Key := 0;
end;
gkDown:
// el de abajo
if (n2 <> map.mapH - 1) then
begin
map.map[n] := map.map[n + map.mapW];
map.map[n + map.mapW] := '0';
Result := True;
Key := 0;
end;
end;
if Result then
exit;
end;
if n1 = map.mapW - 1 then
begin
n1 := -1;
Inc(n2);
end;
Inc(n1);
end;
end;
function IsMapSolved(var map: TGameMap): boolean;
begin
Result := map.map = map.solve;
end;
function CalcularPosibles(str: string; showDebug: boolean = False): TarrayOfString;
function Factorial(number: integer): integer;
var
i: integer;
begin
Result := number;
for i := number - 1 downto 1 do
begin
Result := Result * i;
end;
end;
function MoverIzq(str: string; pos: integer): string;
var
s1, s2: char;
begin
Result := str;
s1 := Result[pos];
s2 := Result[pos - 1];
Result[pos] := s2;
Result[pos - 1] := s1;
end;
function MoverIni(str: string; pos: integer): string;
var
s1, s2: char;
begin
Result := str;
s1 := Result[pos];
s2 := Result[1];
Result[pos] := s2;
Result[1] := s1;
end;
var
nLargo, // numero de char en la string
nFactorial, // numero de combinaciones
nFactorialDivLargo, // primer char veces repetido
nFactorialm1DivLargom1, // segundo char veces repetido
nPosibles: integer; // numero de combinaciones jugables
n: integer;
rstr: string;
begin
// Los comentarios de los numeros son para str:=1230;
nLargo := length(str); // 4
if nLargo <= 1 then
exit;
nFactorial := Factorial(nLargo); // 24
//nFactorialDivLargo := nFactorial div nLargo; // 6
//nFactorialm1DivLargom1 := Factorial(nLargo - 1) div (nLargo - 1); // 2
nPosibles := nFactorial div 2; // 12
SetLength(Result, nPosibles);
//0 to 11
for n := 0 to nPosibles - 1 do
begin
// create a function here ;)
// start with '1'
if n = 0 then
Result[n] := str // 1230 (no clic)
else
if n = 1 then // 1203 (clic 3)
Result[n] := MoverIzq(Result[n - 1], nLargo); // 1203
if n = 2 then // 1032 (clic 2)
begin
Result[n] := MoverIzq(Result[n - 1], nLargo - 1); // 1023
Result[n] := MoverIzq(Result[n], nLargo); // 1032
end;
// start with '2' (the 2 positioned char)
if n = 3 then // 2310 (clic 3 2 1 3)
begin
Result[n] := MoverIni(Result[0], 2); // 2130
Result[n] := MoverIzq(Result[n], nLargo - 1); // 2310
end;
if n = 4 then // 2301
begin
Result[n] := MoverIzq(Result[n - 1], nLargo); // 2301
end;
if n = 5 then // 2013
begin
Result[n] := MoverIzq(Result[n - 1], nLargo - 1); // 2031
Result[n] := MoverIzq(Result[n], nLargo); // 2013
end;
// start with '3' (the 3 positioned char)
if n = 6 then // 3120
begin
Result[n] := MoverIni(Result[0], 3);
Result[n] := MoverIzq(Result[n], nLargo - 1); // 3120
end;
if n = 7 then // 3102
begin
Result[n] := MoverIzq(Result[n - 1], nLargo); // 3102
end;
if n = 8 then // 3021
begin
Result[n] := MoverIzq(Result[n - 1], nLargo - 1);
Result[n] := MoverIzq(Result[n], nLargo); // 3021
end;
// start with '0' (the 4 positioned char)
if n = 9 then // 0321
begin
Result[n] := MoverIni(Result[0], 4);
Result[n] := MoverIzq(Result[n], nLargo - 1); // 0321
end;
if n = 10 then // 0213
begin
Result[n] := MoverIzq(Result[n - 1], nLargo - 1);
Result[n] := MoverIzq(Result[n], nLargo); // 0213
end;
if n = 11 then // 0132
begin
Result[n] := MoverIzq(Result[n - 1], nLargo - 1);
Result[n] := MoverIzq(Result[n], nLargo); // 0232
end;
end;
// Debug
if showDebug then
begin
rstr := '';
for n := 0 to nPosibles - 1 do
begin
rstr := concat(rstr, LineEnding, Result[n]);
end;
ShowMessage(rstr);
ShowMessage(
concat('numero de char en la string: ', IntToStr(nLargo), LineEnding,
'numero de combinaciones: ', IntToStr(nFactorial), LineEnding,
//'primer char veces repetido: ', IntToStr(nFactorialDivLargo),
//LineEnding, 'segundo char veces repetido: ', IntToStr(nFactorialm1DivLargom1), LineEnding,
'numero de combinaciones jugables: ', IntToStr(nPosibles))
);
end;
end;
{$R *.lfm}
{ TForm1 }
procedure TForm1.BGRAVirtualScreen1Redraw(Sender: TObject; Bitmap: TBGRABitmap);
begin
DrawMap(map^, bitmap, texture);
end;
{ MOUSE }
procedure TForm1.BGRAVirtualScreen1Click(Sender: TObject);
var
pos: TPoint;
begin
pos := ScreenToClient(Mouse.CursorPos);
if ClickMap(map^, pos) then
begin
PlayBlockSound;
BGRAVirtualScreen1.DiscardBitmap;
end;
end;
{ CREATE }
procedure TForm1.FormCreate(Sender: TObject);
var
a: TArrayOfString;
i: integer;
begin
a := CalcularPosibles('1230'{, True});
// 0 is 1230 the solve
// 1 to 11
for i := 1 to length(a) - 1 do
begin
Game[i].map := a[i];
Game[i].solve := a[0];
Game[i].mapW := length(a[0]) div 2;
Game[i].mapH := length(a[0]) div 2;
Game[i].blockW := 128;
Game[i].blockH := 128;
end;
Game[length(Game)] := GameOver;
{ SCALING }
// FullScreen with current screen resolution
//Size := Rect(0, 0, Screen.Width, Screen.Height);
Size := Rect(0, 0, 640, 480);
SetBounds(Size.Left, Size.Top, Size.Right, Size.Bottom);
ScaleAspectRatio(BGRAVirtualScreen1, Size.Right, Size.Bottom, True, True, True);
ToggleFullScreen(Self, Size);
ScaleBlocks;
{ OPEN }
randomize; // to randomize the skin
OpenMap(1);
current := 1;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
CloseMap;
end;
{ KEYBOARD }
procedure TForm1.FormKeyDown(Sender: TObject; var Key: word; Shift: TShiftState);
begin
if (Key = VK_ESCAPE) then
Application.Terminate;
if (Key = VK_F11) then
ToggleFullScreen(Self, Size);
if KeyPressMap(map^, Key) then
begin
PlayBlockSound;
BGRAVirtualScreen1.DiscardBitmap;
end;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
CenterControl(BGRAVirtualScreen1);
end;
{ TIMER }
procedure TForm1.Timer1Timer(Sender: TObject);
{$IFDEF WINDOWS}
procedure JoyDown(const pKey: word);
var
Key: word;
begin
Key := pKey;
FormKeyDown(nil, Key, []);
end;
{$ENDIF}
var
nextGame: boolean;
{$IFDEF WINDOWS}
myJoy: TJoyInfoEx;
myJoyCaps: TJoyCaps;
ErrorResultC, ErrorResultP: MMRESULT;
{$ENDIF}
begin
if map <> nil then
begin
Caption := 'Level ' + IntToStr(current);
nextGame := IsMapSolved(map^);
if (current = length(Game)) and nextGame then
begin
Timer1.Enabled := False;
//ShowMessage('Game Win!');
exit;
end;
if nextGame then
begin
Timer1.Enabled := False;
OpenMap(current + 1);
//ShowMessage('Next: Level ' + IntToStr(current));
BGRAVirtualScreen1.DiscardBitmap;
Timer1.Enabled := True;
end;
{$IFDEF WINDOWS}
ErrorResultC := joyGetDevCaps(joystickid1, @myJoyCaps, sizeof(myJoyCaps));
ErrorResultP := joyGetPosEx(joystickid1, @MyJoy);
if (ErrorResultC = JOYERR_NOERROR) and (ErrorResultP = JOYERR_NOERROR) then
begin
if (myJoy.dwPOV = JOY_POVFORWARD) or (myJoy.wYpos = myJoyCaps.wYmin) then
JoyDown(U1)
else if (myJoy.dwPOV = JOY_POVBACKWARD) or (myJoy.wYpos = myJoyCaps.wYmax) then
JoyDown(D1)
else if (myJoy.dwPOV = JOY_POVLEFT) or (myJoy.wXpos = myJoyCaps.wXmin) then
JoyDown(L1)
else if (myJoy.dwPOV = JOY_POVRIGHT) or (myJoy.wXpos = myJoyCaps.wXmax) then
JoyDown(R1);
end;
{$ENDIF}
end;
end;
procedure TForm1.OpenMap(num: integer);
begin
CloseMap;
if (num < low(Game)) or (num > high(Game)) then
halt;
current := num;
map := @Game[current];
GenerateTexture;
end;
procedure TForm1.CloseMap;
begin
if map <> nil then
FreeAndNil(map^.background);
FreeAndNil(texture);
map := nil;
end;
procedure TForm1.ScaleBlocks;
var
i: integer;
begin
for i := low(game) to high(game) do
begin
Game[i].blockW := BGRAVirtualScreen1.Width div Game[i].mapW;
Game[i].blockH := BGRAVirtualScreen1.Height div Game[i].mapH;
end;
end;
procedure TForm1.GenerateTexture;
begin
if texture <> nil then
raise Exception.Create('Texture not freed');
case random(9) of
0: texture := CreatePlastikTexture(BGRAVirtualScreen1.Width,
BGRAVirtualScreen1.Height);
1: texture := CreateCamouflageTexture(BGRAVirtualScreen1.Width,
BGRAVirtualScreen1.Height);
2: texture := CreateSnowPrintTexture(BGRAVirtualScreen1.Width,
BGRAVirtualScreen1.Height);
3: texture := CreateRoundStoneTexture(BGRAVirtualScreen1.Width,
BGRAVirtualScreen1.Height);
4: texture := CreateStoneTexture(BGRAVirtualScreen1.Width,
BGRAVirtualScreen1.Height);
5: texture := CreateWaterTexture(BGRAVirtualScreen1.Width,
BGRAVirtualScreen1.Height);
6: texture := CreateMarbleTexture(BGRAVirtualScreen1.Width,
BGRAVirtualScreen1.Height);
7: texture := CreateWoodTexture(BGRAVirtualScreen1.Width, BGRAVirtualScreen1.Height);
8: texture := CreateVerticalWoodTexture(BGRAVirtualScreen1.Width,
BGRAVirtualScreen1.Height);
end;
end;
procedure TForm1.PlayBlockSound;
begin
{$IFDEF WINDOWS}
PlaySound(PChar('move.wav'), 0, SND_ASYNC);
{$ENDIF}
end;
end.