summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--eepers.adb329
-rw-r--r--raylib.ads5
2 files changed, 200 insertions, 134 deletions
diff --git a/eepers.adb b/eepers.adb
index ca84416..98b68ca 100644
--- a/eepers.adb
+++ b/eepers.adb
@@ -7,7 +7,6 @@ with Ada.Strings.Unbounded;
use Ada.Strings.Unbounded;
with Ada.Containers.Vectors;
with Ada.Unchecked_Deallocation;
-with Ada.Containers.Hashed_Maps;
use Ada.Containers;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
with Ada.Strings;
@@ -144,7 +143,8 @@ procedure Eepers is
Put_Line("WARNING: could not load colors from file " & File_Name & ": " & Exception_Message(E));
end;
- TURN_DURATION_SECS : constant Float := 0.125;
+ BASE_TURN_DURATION_SECS : constant Float := 0.125;
+ TURN_DURATION_SECS : Float := BASE_TURN_DURATION_SECS;
GUARD_ATTACK_COOLDOWN : constant Integer := 10;
EEPER_EXPLOSION_DAMAGE : constant Float := 0.45;
GUARD_TURN_REGENERATION : constant Float := 0.01;
@@ -210,33 +210,14 @@ procedure Eepers is
return (A.X*S, A.Y*S);
end;
- function Equivalent_IVector2(Left, Right: IVector2) return Boolean is
- begin
- return Left.X = Right.X and then Left.Y = Right.Y;
- end;
-
- function Hash_IVector2(V: IVector2) return Hash_Type is
- M31: constant Hash_Type := 2**31-1; -- a nice Mersenne prime
- begin
- return Hash_Type(V.X) * M31 + Hash_Type(V.Y);
- end;
-
- type Item_Kind is (Item_Key, Item_Bomb_Gen, Item_Checkpoint);
-
- type Item(Kind: Item_Kind := Item_Key) is record
- case Kind is
- when Item_Bomb_Gen =>
- Cooldown: Integer;
- when others => null;
- end case;
+ type Item_Kind is (Item_None, Item_Key, Item_Bomb_Gen, Item_Checkpoint);
+ type Item is record
+ Kind: Item_Kind := Item_None;
+ Position: IVector2;
+ Cooldown: Integer;
end record;
-
- package Hashed_Map_Items is new
- Ada.Containers.Hashed_Maps(
- Key_Type => IVector2,
- Element_Type => Item,
- Hash => Hash_IVector2,
- Equivalent_Keys => Equivalent_IVector2);
+ type Item_Index is range 1..100;
+ type Item_Array is array (Item_Index) of Item;
function To_Vector2(iv: IVector2) return Vector2 is
begin
@@ -325,7 +306,7 @@ procedure Eepers is
Player_Bombs: Integer;
Player_Bomb_Slots: Integer;
Eepers: Eeper_Array;
- Items: Hashed_Map_Items.Map;
+ Items: Item_Array;
Bombs: Bomb_State_Array;
end record;
@@ -336,7 +317,7 @@ procedure Eepers is
Turn_Animation: Float := 0.0;
- Items: Hashed_Map_Items.Map;
+ Items: Item_Array;
Bombs: Bomb_State_Array;
Camera_Position: Vector2 := (x => 0.0, y => 0.0);
@@ -497,9 +478,8 @@ procedure Eepers is
Game.Bombs := Game.Checkpoint.Bombs;
end;
- Too_Many_Entities: exception;
-
function Allocate_Eeper(Game: in out Game_State) return Eeper_Index is
+ Too_Many_Entities: exception;
begin
for Eeper in Game.Eepers'Range loop
if Game.Eepers(Eeper).Dead then
@@ -510,6 +490,20 @@ procedure Eepers is
raise Too_Many_Entities;
end;
+ procedure Allocate_Item(Game: in out Game_State; Position: IVector2; Kind: Item_Kind) is
+ Too_Many_Items: exception;
+ begin
+ for Item of Game.Items loop
+ if Item.Kind = Item_None then
+ Item.Kind := Kind;
+ Item.Position := Position;
+ Item.Cooldown := 0;
+ return;
+ end if;
+ end loop;
+ raise Too_Many_Items;
+ end;
+
procedure Spawn_Gnome(Game: in out Game_State; Position: IVector2) is
Gnome: Eeper_State renames Game.Eepers(Allocate_Eeper(Game));
Size: constant IVector2 := (1, 1);
@@ -646,7 +640,9 @@ procedure Eepers is
end loop;
end loop;
- Game.Items.Clear;
+ for Item of Game.Items loop
+ Item.Kind := Item_None;
+ end loop;
for Bomb of Game.Bombs loop
Bomb.Countdown := 0;
end loop;
@@ -682,15 +678,15 @@ procedure Eepers is
when Level_Door => Game.Map(Row, Column) := Cell_Door;
when Level_Checkpoint =>
Game.Map(Row, Column) := Cell_Floor;
- Game.Items.Insert((Column, Row), (Kind => Item_Checkpoint));
+ Allocate_Item(Game, (Column, Row), Item_Checkpoint);
when Level_Bomb_Gen =>
Game.Map(Row, Column) := Cell_Floor;
- Game.Items.Insert((Column, Row), (Kind => Item_Bomb_Gen, Cooldown => 0));
+ Allocate_Item(Game, (Column, Row), Item_Bomb_Gen);
when Level_Barricade =>
Game.Map(Row, Column) := Cell_Barricade;
when Level_Key =>
Game.Map(Row, Column) := Cell_Floor;
- Game.Items.Insert((Column, Row), (Kind => Item_Key));
+ Allocate_Item(Game, (Column, Row), Item_Key);
when Level_Player =>
Game.Map(Row, Column) := Cell_Floor;
if Update_Player then
@@ -754,23 +750,23 @@ procedure Eepers is
end;
procedure Game_Items(Game: in Game_State) is
- use Hashed_Map_Items;
begin
- for C in Game.Items.Iterate loop
- case Element(C).Kind is
- when Item_Key => Draw_Key(Key(C));
+ for Item of Game.Items loop
+ case Item.Kind is
+ when Item_None => null;
+ when Item_Key => Draw_Key(Item.Position);
when Item_Checkpoint =>
declare
Checkpoint_Item_Size: constant Vector2 := Cell_Size*0.5;
begin
- Draw_Rectangle_V(To_Vector2(Key(C))*Cell_Size + Cell_Size*0.5 - Checkpoint_Item_Size*0.5, Checkpoint_Item_Size, Palette_RGB(COLOR_CHECKPOINT));
+ Draw_Rectangle_V(To_Vector2(Item.Position)*Cell_Size + Cell_Size*0.5 - Checkpoint_Item_Size*0.5, Checkpoint_Item_Size, Palette_RGB(COLOR_CHECKPOINT));
end;
when Item_Bomb_Gen =>
- if Element(C).Cooldown > 0 then
- Draw_Bomb(Key(C), Color_Brightness(Palette_RGB(COLOR_BOMB), -0.5));
- Draw_Number(Key(C), Element(C).Cooldown, Palette_RGB(COLOR_LABEL));
+ if Item.Cooldown > 0 then
+ Draw_Bomb(Item.Position, Color_Brightness(Palette_RGB(COLOR_BOMB), -0.5));
+ Draw_Number(Item.Position, Item.Cooldown, Palette_RGB(COLOR_LABEL));
else
- Draw_Bomb(Key(C), Palette_RGB(COLOR_BOMB));
+ Draw_Bomb(Item.Position, Palette_RGB(COLOR_BOMB));
end if;
end case;
end loop;
@@ -825,32 +821,30 @@ procedure Eepers is
case Game.Map(New_Position.Y, New_Position.X) is
when Cell_Floor =>
Game.Player.Position := New_Position;
- declare
- use Hashed_Map_Items;
- C: Cursor := Game.Items.Find(New_Position);
- begin
- if Has_Element(C) then
- case Element(C).Kind is
+ for Item of Game.Items loop
+ if Item.Position = New_Position then
+ case Item.Kind is
+ when Item_None => null;
when Item_Key =>
Game.Player.Keys := Game.Player.Keys + 1;
- Game.Items.Delete(C);
+ Item.Kind := Item_None;
Play_Sound(Key_Pickup_Sound);
when Item_Bomb_Gen => if
Game.Player.Bombs < Game.Player.Bomb_Slots
- and then Element(C).Cooldown <= 0
+ and then Item.Cooldown <= 0
then
Game.Player.Bombs := Game.Player.Bombs + 1;
- Game.Items.Replace_Element(C, (Kind => Item_Bomb_Gen, Cooldown => BOMB_GENERATOR_COOLDOWN));
+ Item.Cooldown := BOMB_GENERATOR_COOLDOWN;
Play_Sound(Bomb_Pickup_Sound);
end if;
when Item_Checkpoint =>
- Game.Items.Delete(C);
+ Item.Kind := Item_None;
Game.Player.Bombs := Game.Player.Bomb_Slots;
Game_Save_Checkpoint(Game);
Play_Sound(Checkpoint_Sound);
end case;
end if;
- end;
+ end loop;
when Cell_Door =>
if Game.Player.Keys > 0 then
Game.Player.Keys := Game.Player.Keys - 1;
@@ -934,15 +928,49 @@ procedure Eepers is
return Vector2_Lerp(Prev_Position, Curr_Position, C_Float(1.0 - T*T));
end;
- Space_Pressed: Boolean := False;
- Dir_Pressed: array (Direction) of Boolean := [others => False];
+ type Command_Kind is (Command_Step, Command_Plant);
+ type Command(Kind: Command_Kind := Command_Step) is record
+ case Kind is
+ when Command_Step => Dir: Direction;
+ when Command_Plant => null;
+ end case;
+ end record;
+ Command_Capacity: constant Natural := 3;
+ type Command_Array is array (0..Command_Capacity-1) of Command;
+ type Command_Queue_Record is record
+ Items: Command_Array;
+ Start: Natural := 0;
+ Size: Natural := 0;
+ end record;
+
+ procedure Command_Enqueue(Q: in out Command_Queue_Record; C: Command) is
+ begin
+ Q.Items((Q.Start + Q.Size) mod Command_Capacity) := C;
+ if Q.Size < Command_Capacity then
+ Q.Size := Q.Size + 1;
+ else
+ Q.Start := (Q.Start + 1) mod Command_Capacity;
+ end if;
+ end;
+
+ function Command_Dequeue(Q: in out Command_Queue_Record; C: out Command) return Boolean is
+ begin
+ if Q.Size = 0 then
+ return False;
+ end if;
+ C := Q.Items(Q.Start);
+ Q.Size := Q.Size - 1;
+ Q.Start := (Q.Start + 1) mod Command_Capacity;
+ return True;
+ end;
+
+ Command_Queue: Command_Queue_Record;
Any_Key_Pressed: Boolean := False;
procedure Swallow_Player_Input is
begin
- Space_Pressed := False;
+ Command_Queue.Size := 0;
Any_Key_Pressed := False;
- Dir_Pressed := [others => False];
end;
procedure Game_Bombs_Turn(Game: in out Game_State) is
@@ -981,7 +1009,7 @@ procedure Eepers is
end if;
when Eeper_Gnome =>
Eeper.Dead := True;
- Game.Items.Insert(Eeper.Position, (Kind => Item_Key));
+ Allocate_Item(Game, Eeper.Position, Item_Key);
end case;
end if;
end loop;
@@ -1126,12 +1154,11 @@ procedure Eepers is
end;
procedure Game_Items_Turn(Game: in out Game_State) is
- use Hashed_Map_Items;
begin
- for C in Game.Items.Iterate loop
- if Element(C).Kind = Item_Bomb_Gen then
- if Element(C).Cooldown > 0 then
- Game.Items.Replace_Element(C, (Kind => Item_Bomb_Gen, Cooldown => Element(C).Cooldown - 1));
+ for Item of Game.Items loop
+ if Item.Kind = Item_Bomb_Gen then
+ if Item.Cooldown > 0 then
+ Item.Cooldown := Item.Cooldown - 1;
end if;
end if;
end loop;
@@ -1221,53 +1248,57 @@ procedure Eepers is
return;
end if;
- if Space_Pressed and then Game.Player.Bombs > 0 then
- declare
- Start_Of_Turn: constant Double := Get_Time;
- begin
- Game.Turn_Animation := 1.0;
- Game_Explosions_Turn(Game);
- Game_Items_Turn(Game);
-
- -- Game_Player_Turn(Game, Action_Plant_Bomb, Left);
- Game.Player.Prev_Eyes := Game.Player.Eyes;
- Game.Player.Prev_Position := Game.Player.Position;
-
- Game_Eepers_Turn(Game);
- Game_Bombs_Turn(Game);
-
- if Game.Player.Bombs > 0 then
- for Bomb of Game.Bombs loop
- if Bomb.Countdown <= 0 then
- Bomb.Countdown := 3;
- Bomb.Position := Game.Player.Position;
- exit;
- end if;
- end loop;
- Game.Player.Bombs := Game.Player.Bombs - 1;
- Play_Sound(Plant_Bomb_Sound);
- end if;
+ declare
+ C: Command;
+ begin
+ if Command_Dequeue(Command_Queue, C) then
+ case C.Kind is
+ when Command_Step =>
+ declare
+ Start_Of_Turn: constant Double := Get_Time;
+ begin
+ Game.Turn_Animation := 1.0;
+ Game_Explosions_Turn(Game);
+ Game_Items_Turn(Game);
+ Game_Player_Turn(Game, C.Dir);
+ Game_Eepers_Turn(Game);
+ Game_Bombs_Turn(Game);
+ Game.Duration_Of_Last_Turn := Get_Time - Start_Of_Turn;
+ end;
+ when Command_Plant =>
+ if Game.Player.Bombs > 0 then
+ declare
+ Start_Of_Turn: constant Double := Get_Time;
+ begin
+ Game.Turn_Animation := 1.0;
+ Game_Explosions_Turn(Game);
+ Game_Items_Turn(Game);
+
+ -- Game_Player_Turn(Game, Action_Plant_Bomb, Left);
+ Game.Player.Prev_Eyes := Game.Player.Eyes;
+ Game.Player.Prev_Position := Game.Player.Position;
+
+ Game_Eepers_Turn(Game);
+ Game_Bombs_Turn(Game);
+
+ if Game.Player.Bombs > 0 then
+ for Bomb of Game.Bombs loop
+ if Bomb.Countdown <= 0 then
+ Bomb.Countdown := 3;
+ Bomb.Position := Game.Player.Position;
+ exit;
+ end if;
+ end loop;
+ Game.Player.Bombs := Game.Player.Bombs - 1;
+ Play_Sound(Plant_Bomb_Sound);
+ end if;
- Game.Duration_Of_Last_Turn := Get_Time - Start_Of_Turn;
- end;
- else
- for Dir in Direction loop
- if Dir_Pressed(Dir) then
- declare
- Start_Of_Turn: constant Double := Get_Time;
- begin
- Game.Turn_Animation := 1.0;
- Game_Explosions_Turn(Game);
- Game_Items_Turn(Game);
- Game_Player_Turn(Game, Dir);
- Game_Eepers_Turn(Game);
- Game_Bombs_Turn(Game);
- Game.Duration_Of_Last_Turn := Get_Time - Start_Of_Turn;
- exit;
- end;
- end if;
- end loop;
- end if;
+ Game.Duration_Of_Last_Turn := Get_Time - Start_Of_Turn;
+ end;
+ end if;
+ end case;
+ end if;
+ end;
end;
procedure Game_Bombs(Game: Game_State) is
@@ -1374,14 +1405,13 @@ procedure Eepers is
end;
Game: Game_State;
- Title: constant Char_Array := To_C("Eepers (v1.0)");
+ Title: constant Char_Array := To_C("Eepers (v1.2)");
Palette_Editor: Boolean := False;
Palette_Editor_Choice: Palette := Palette'First;
Palette_Editor_Selected: Boolean := False;
Palette_Editor_Component: HSV_Comp := Hue;
Icon: Image;
-
begin
if not Change_Directory(Get_Application_Directory) then
Put_Line("WARNING: Could not change working directory to the application directory");
@@ -1425,19 +1455,55 @@ begin
Begin_Drawing;
Clear_Background(Palette_RGB(COLOR_BACKGROUND));
- Swallow_Player_Input;
+ if Game.Player.Dead then
+ Command_Queue.Size := 0;
+ else
+ if Boolean(Is_Key_Down(KEY_LEFT_SHIFT)) and then Game.Turn_Animation <= 0.0 then
+ if Is_Key_Down(KEY_A) or else Is_Key_Down(KEY_LEFT) then
+ Command_Queue.Size := 0;
+ Command_Enqueue(Command_Queue, (Kind => Command_Step, Dir => Left));
+ end if;
+ if Is_Key_Down(KEY_D) or else Is_Key_Down(KEY_RIGHT) then
+ Command_Queue.Size := 0;
+ Command_Enqueue(Command_Queue, (Kind => Command_Step, Dir => Right));
+ end if;
+ if Is_Key_Down(KEY_S) or else Is_Key_Down(KEY_DOWN) then
+ Command_Queue.Size := 0;
+ Command_Enqueue(Command_Queue, (Kind => Command_Step, Dir => Down));
+ end if;
+ if Is_Key_Down(KEY_W) or else Is_Key_Down(KEY_UP) then
+ Command_Queue.Size := 0;
+ Command_Enqueue(Command_Queue, (Kind => Command_Step, Dir => Up));
+ end if;
+ else
+ if Is_Key_Pressed(KEY_A) or else Is_Key_Pressed(KEY_LEFT) then
+ Command_Enqueue(Command_Queue, (Kind => Command_Step, Dir => Left));
+ end if;
+ if Is_Key_Pressed(KEY_D) or else Is_Key_Pressed(KEY_RIGHT) then
+ Command_Enqueue(Command_Queue, (Kind => Command_Step, Dir => Right));
+ end if;
+ if Is_Key_Pressed(KEY_S) or else Is_Key_Pressed(KEY_DOWN) then
+ Command_Enqueue(Command_Queue, (Kind => Command_Step, Dir => Down));
+ end if;
+ if Is_Key_Pressed(KEY_W) or else Is_Key_Pressed(KEY_UP) then
+ Command_Enqueue(Command_Queue, (Kind => Command_Step, Dir => Up));
+ end if;
+ end if;
+ if Is_Key_Pressed(KEY_SPACE) then
+ Command_Enqueue(Command_Queue, (Kind => Command_Plant));
+ end if;
+ end if;
if Is_Key_Down(KEY_LEFT_SHIFT) then
- Dir_Pressed(Left) := Boolean(Is_Key_Down(KEY_A)) or else Boolean(Is_Key_Down(KEY_LEFT));
- Dir_Pressed(Right) := Boolean(Is_Key_Down(KEY_D)) or else Boolean(Is_Key_Down(KEY_RIGHT));
- Dir_Pressed(Down) := Boolean(Is_Key_Down(KEY_S)) or else Boolean(Is_Key_Down(KEY_DOWN));
- Dir_Pressed(Up) := Boolean(Is_Key_Down(KEY_W)) or else Boolean(Is_Key_Down(KEY_UP));
+ TURN_DURATION_SECS := BASE_TURN_DURATION_SECS * 0.8;
else
- Dir_Pressed(Left) := Boolean(Is_Key_Pressed(KEY_A)) or else Boolean(Is_Key_Pressed(KEY_LEFT));
- Dir_Pressed(Right) := Boolean(Is_Key_Pressed(KEY_D)) or else Boolean(Is_Key_Pressed(KEY_RIGHT));
- Dir_Pressed(Down) := Boolean(Is_Key_Pressed(KEY_S)) or else Boolean(Is_Key_Pressed(KEY_DOWN));
- Dir_Pressed(Up) := Boolean(Is_Key_Pressed(KEY_W)) or else Boolean(Is_Key_Pressed(KEY_UP));
+ if Command_Queue.Size /= 0 then
+ TURN_DURATION_SECS := BASE_TURN_DURATION_SECS * (1.0 / Float(Command_Queue.Size));
+ else
+ TURN_DURATION_SECS := BASE_TURN_DURATION_SECS;
+ end if;
end if;
- Space_Pressed := Boolean(Is_Key_Pressed(KEY_SPACE));
+
+ Any_Key_Pressed := False;
while not Any_Key_Pressed and then Get_Key_Pressed /= KEY_NULL loop
Any_Key_Pressed := True;
end loop;
@@ -1584,9 +1650,6 @@ end;
-- TODO: If you are standing on the refilled bomb gen and place a bomb you should refill your bomb in that turn.
-- TODO: Checkpoints should be circles (like all the items)
-- TODO: Custom font
--- TODO: Determenistically choose the paths again, but make them more interesting
--- - Gnomes are just being deterministic
--- - Mother and Guard always pick the longest path. Or generally the path that brings the Euclidean Distance closer
-- TODO: Mother should require several attacks before being "split"
-- TODO: Enemies should attack on zero just like a bomb.
-- TODO: Properly disablable DEV features
@@ -1596,8 +1659,11 @@ end;
-- TODO: Visual Clue that the Eeper is about to kill the Player when Completely outside of the Screen
-- - Cooldown ball is shaking
-- TODO: Cool animation for New Game
--- TODO: Tutorial sign that says "WASD" to move when you start the game for the first time. And how to place the bomb on picking it up
--- TODO: count the player's turns towards the final score of the game
+-- TODO: Tutorial
+-- - Sign that says "WASD" to move when you start the game for the first time.
+-- - And how to place the bomb on picking it up.
+-- - How to sprint after you blow up firt Barricade.
+-- TODO: Count the player's turns towards the final score of the game
-- We can even collect different stats, like bombs collected, bombs used,
-- times died etc.
-- TODO: Animate key when you pick it up
@@ -1610,18 +1676,13 @@ end;
-- - Cool effects when you pick up items and checkpoints
-- TODO: Camera shaking when big bosses (Guard and Mother) make moves
-- TODO: Menu
--- Could be just a splash with the game name and logo.
-- TODO: WebAssembly build
-- https://blog.adacore.com/use-of-gnat-llvm-to-translate-ada-applications-to-webassembly
-- TODO: Explosions should trigger other primed bombs?
-- TODO: Path finding in a separate thread
-- TODO: Eeper slide attack animation is pretty boring @polish
--- TODO: Bake assets into executable
-- TODO: Background is too boring
-- Maybe some sort of repeating pattern would be better.
-- TODO: Indicate how many bomb slots we have in HUD
-- TODO: Eyes of Father changing as the Player gets closer:
-- - Happy (very important to indicate that he's not hostile)
--- TODO: Transition Player's eyes linearly in Euclidean space instead of angularly.
--- TODO: Can you escape boss rooms using Gnomes?
--- It's very hard because you need to somehow put them behind yourself
diff --git a/raylib.ads b/raylib.ads
index e3c987e..2c8415f 100644
--- a/raylib.ads
+++ b/raylib.ads
@@ -85,6 +85,11 @@ package Raylib is
Import => True,
Convention => C,
External_Name => "IsKeyPressed";
+ function Is_Key_Released(key: int) return C_bool
+ with
+ Import => True,
+ Convention => C,
+ External_Name => "IsKeyReleased";
function Get_Key_Pressed return int
with
Import => True,