diff options
Diffstat (limited to 'eepers.adb')
-rw-r--r-- | eepers.adb | 329 |
1 files changed, 195 insertions, 134 deletions
@@ -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 |