From 3f4cc25e365d5702b28778f208483f9afd3f7f97 Mon Sep 17 00:00:00 2001 From: rexim Date: Mon, 18 Mar 2024 03:32:52 +0700 Subject: Introduce Gnomes --- colors.txt | 1 + game.adb | 296 +++++++++++++++++++++++++++++++++++++++++-------------------- map.txt | 46 +++++----- test.adb | 24 ++--- 4 files changed, 230 insertions(+), 137 deletions(-) diff --git a/colors.txt b/colors.txt index 50e06ca..4d6f27e 100644 --- a/colors.txt +++ b/colors.txt @@ -8,6 +8,7 @@ Bomb 0 186 255 Label 0 0 255 Shrek 60 255 255 Urmom 242 196 212 +Gnome 65 194 255 Checkpoint 213 255 255 Explosion 213 255 255 Healthbar 0 255 255 diff --git a/game.adb b/game.adb index 34ac5fb..b933980 100644 --- a/game.adb +++ b/game.adb @@ -12,9 +12,17 @@ use Ada.Containers; with Ada.Strings.Fixed; use Ada.Strings.Fixed; with Ada.Strings; with Ada.Exceptions; use Ada.Exceptions; +with Ada.Numerics.Discrete_Random; procedure Game is + package Random_Integer is + new Ada.Numerics.Discrete_Random(Result_Subtype => Integer); + + use Random_Integer; + + Gen: Generator; DEVELOPMENT : constant Boolean := True; + Not_Implemented: exception; type Palette is ( COLOR_BACKGROUND, @@ -27,6 +35,7 @@ procedure Game is COLOR_LABEL, COLOR_SHREK, COLOR_URMOM, + COLOR_GNOME, COLOR_CHECKPOINT, COLOR_EXPLOSION, COLOR_HEALTHBAR); @@ -42,6 +51,7 @@ procedure Game is COLOR_LABEL => To_Unbounded_String("Label"), COLOR_SHREK => To_Unbounded_String("Shrek"), COLOR_URMOM => To_Unbounded_String("Urmom"), + COLOR_GNOME => To_Unbounded_String("Gnome"), COLOR_CHECKPOINT => To_Unbounded_String("Checkpoint"), COLOR_EXPLOSION => To_Unbounded_String("Explosion"), COLOR_HEALTHBAR => To_Unbounded_String("Healthbar") @@ -237,18 +247,19 @@ procedure Game is Dead: Boolean := False; end record; - type Boss_Behavior is (Shrek, Urmom); + type Boss_Behavior is (Shrek, Urmom, Gnome); type Boss_State is record Behavior: Boss_Behavior; Dead: Boolean := True; Prev_Position: IVector2; Position: IVector2; + Size: IVector2; + Path: Path_Map_Access; + Background: Palette; - Size: IVector2 := (3, 3); Health: Float := 1.0; Attack_Cooldown: Integer := SHREK_ATTACK_COOLDOWN; - Path: Path_Map_Access; end record; type Bomb_State is record @@ -351,7 +362,8 @@ procedure Game is (Game: in out Game_State; Me: Boss_Index; Steps_Limit: Integer; - Step_Length_Limit: Integer) + Step_Length_Limit: Integer; + Stop_At_Me: Boolean := True) is Q: Queue.Vector; begin @@ -380,7 +392,7 @@ procedure Game is begin Q.Delete_First; - if Position = Game.Bosses(Me).Position then + if Stop_At_Me and then Position = Game.Bosses(Me).Position then exit; end if; @@ -437,6 +449,37 @@ procedure Game is Game.Items := Game.Checkpoint.Items; end; + procedure Spawn_Gnome(Game: in out Game_State; Position: IVector2) is + begin + for Boss of Game.Bosses loop + if Boss.Dead then + Boss.Behavior := Gnome; + Boss.Dead := False; + Boss.Background := COLOR_GNOME; + Boss.Position := Position; + Boss.Prev_Position := Position; + Boss.Size := (1, 1); + exit; + end if; + end loop; + end; + + procedure Spawn_Urmom(Game: in out Game_State; Position: IVector2) is + begin + for Boss of Game.Bosses loop + if Boss.Dead then + Boss.Behavior := Urmom; + Boss.Dead := False; + Boss.Background := COLOR_URMOM; + Boss.Position := Position; + Boss.Prev_Position := Position; + Boss.Health := 1.0; + Boss.Size := (6, 6); + exit; + end if; + end loop; + end; + procedure Spawn_Shrek(Game: in out Game_State; Position: IVector2) is begin for Boss of Game.Bosses loop @@ -503,19 +546,11 @@ procedure Game is for Column in Game.Map'Range(2) loop if Column in 1..Length(Map_Row) then case Element(Map_Row, Column) is + when 'G' => + Spawn_Gnome(Game, (Column, Row)); + Game.Map(Row, Column) := Floor; when 'M' => - for Boss of Game.Bosses loop - if Boss.Dead then - Boss.Behavior := Urmom; - Boss.Dead := False; - Boss.Background := COLOR_URMOM; - Boss.Position := (Column, Row); - Boss.Prev_Position := (Column, Row); - Boss.Health := 1.0; - Boss.Size := (6, 6); - exit; - end if; - end loop; + Spawn_Urmom(Game, (Column, Row)); Game.Map(Row, Column) := Floor; when 'B' => Spawn_Shrek(Game, (Column, Row)); @@ -583,9 +618,6 @@ procedure Game is Position: constant Vector2 := To_Vector2((Column, Row))*Cell_Size; begin Draw_Rectangle_V(position, cell_size, Cell_Colors(Game.Map(Row, Column))); - if Is_Key_Down(KEY_P) then - Draw_Number((Column, Row), Game.Bosses(1).Path(Row, Column), (A => 255, others => 0)); - end if; end; end loop; end loop; @@ -644,7 +676,7 @@ procedure Game is end loop; end; - procedure Player_Step(Game: in out Game_State; Dir: Direction) is + procedure Game_Player_Turn(Game: in out Game_State; Dir: Direction) is New_Position: constant IVector2 := Game.Player.Position + Direction_Vector(Dir); begin Game.Player.Prev_Position := Game.Player.Position; @@ -707,6 +739,9 @@ procedure Game is for Boss of Game.Bosses loop if not Boss.Dead and then Inside_Of_Rect(Boss.Position, Boss.Size, New_Position) then case Boss.Behavior is + when Gnome => + Game.Items.Insert(Boss.Position, (Kind => Key)); + Boss.Dead := True; when Shrek => Boss.Health := Boss.Health - BOSS_EXPLOSION_DAMAGE; if Boss.Health <= 0.0 then @@ -787,6 +822,118 @@ procedure Game is Dir_Pressed := [others => False]; end; + procedure Game_Bombs_Turn(Game: in out Game_State) is + begin + for Bomb of Game.Bombs loop + if Bomb.Countdown > 0 then + Bomb.Countdown := Bomb.Countdown - 1; + if Bomb.Countdown <= 0 then + Explode(Game, Bomb.Position); + end if; + end if; + end loop; + end; + + procedure Game_Explosions_Turn(Game: in out Game_State) is + begin + for Y in Game.Map'Range(1) loop + for X in Game.Map'Range(2) loop + if Game.Map(Y, X) = Explosion then + Game.Map(Y, X) := Floor; + end if; + end loop; + end loop; + end; + + procedure Game_Bosses_Turn(Game: in out Game_State) is + begin + for Me in Boss_Index loop + if not Game.Bosses(Me).Dead then + Game.Bosses(Me).Prev_Position := Game.Bosses(Me).Position; + case Game.Bosses(Me).Behavior is + when Shrek | Urmom => + Recompute_Path_For_Boss(Game, Me, SHREK_STEPS_LIMIT, SHREK_STEP_LENGTH_LIMIT); + -- TODO: Boss should attack on zero just like a bomb. + if Game.Bosses(Me).Attack_Cooldown <= 0 then + declare + Current : constant Integer := Game.Bosses(Me).Path(Game.Bosses(Me).Position.Y, Game.Bosses(Me).Position.X); + begin + -- TODO: maybe pick the paths + -- randomly to introduce a bit of + -- RNG into this pretty + -- deterministic game + Search: for Dir in Direction loop + declare + Position: IVector2 := Game.Bosses(Me).Position; + begin + while Boss_Can_Stand_Here(Game, Position, Me) loop + Position := Position + Direction_Vector(Dir); + if Within_Map(Game, Position) and then Game.Bosses(Me).Path(Position.Y, Position.X) = Current - 1 then + Game.Bosses(Me).Position := Position; + exit Search; + end if; + end loop; + end; + end loop Search; + end; + Game.Bosses(Me).Attack_Cooldown := SHREK_ATTACK_COOLDOWN; + else + Game.Bosses(Me).Attack_Cooldown := Game.Bosses(Me).Attack_Cooldown - 1; + end if; + + if Inside_Of_Rect(Game.Bosses(Me).Position, Game.Bosses(Me).Size, Game.Player.Position) then + Game.Player.Dead := True; + end if; + if Game.Bosses(Me).Health < 1.0 then + Game.Bosses(Me).Health := Game.Bosses(Me).Health + SHREK_TURN_REGENERATION; + end if; + when Gnome => + Recompute_Path_For_Boss(Game, Me, 10, 1, Stop_At_Me => False); + declare + Position: IVector2 := Game.Bosses(Me).Position; + begin + if Game.Bosses(Me).Path(Position.Y, Position.X) >= 0 then + declare + Available_Positions: array (0..Direction_Vector'Length-1) of IVector2; + Count: Integer := 0; + begin + for Dir in Direction loop + declare + New_Position: constant IVector2 := Position + Direction_Vector(Dir); + begin + if Within_Map(Game, New_Position) + and then Game.Map(New_Position.Y, New_Position.X) = Floor + and then Game.Bosses(Me).Path(New_Position.Y, New_Position.X) > Game.Bosses(Me).Path(Position.Y, Position.X) + then + Available_Positions(Count) := New_Position; + Count := Count + 1; + end if; + end; + end loop; + + if Count > 0 then + Game.Bosses(Me).Position := Available_Positions(Random(Gen) mod Count); + end if; + end; + end if; + end; + end case; + end if; + end loop; + 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 = Bomb then + if Element(C).Cooldown > 0 then + Game.Items.Replace_Element(C, (Kind => Bomb, Cooldown => Element(C).Cooldown - 1)); + end if; + end if; + end loop; + end; + procedure Game_Player(Game: in out Game_State) is begin if Game.Player.Dead then @@ -833,77 +980,11 @@ procedure Game is declare Start_Of_Turn: constant Double := Get_Time; begin - for Y in Game.Map'Range(1) loop - for X in Game.Map'Range(2) loop - if Game.Map(Y, X) = Explosion then - Game.Map(Y, X) := Floor; - end if; - end loop; - end loop; - - Player_Step(Game, Dir); - - for Bomb of Game.Bombs loop - if Bomb.Countdown > 0 then - Bomb.Countdown := Bomb.Countdown - 1; - if Bomb.Countdown <= 0 then - Explode(Game, Bomb.Position); - end if; - end if; - end loop; - - declare - use Hashed_Map_Items; - begin - for C in Game.Items.Iterate loop - if Element(C).Kind = Bomb then - if Element(C).Cooldown > 0 then - Game.Items.Replace_Element(C, (Kind => Bomb, Cooldown => Element(C).Cooldown - 1)); - end if; - end if; - end loop; - end; - - for Me in Boss_Index loop - if not Game.Bosses(Me).Dead then - Recompute_Path_For_Boss(Game, Me, SHREK_STEPS_LIMIT, SHREK_STEP_LENGTH_LIMIT); - Game.Bosses(Me).Prev_Position := Game.Bosses(Me).Position; - -- TODO: Boss should attack on zero just like a bomb. - if Game.Bosses(Me).Attack_Cooldown <= 0 then - declare - Current : constant Integer := Game.Bosses(Me).Path(Game.Bosses(Me).Position.Y, Game.Bosses(Me).Position.X); - begin - -- TODO: maybe pick the paths - -- randomly to introduce a bit of - -- RNG into this pretty - -- deterministic game - Search: for Dir in Direction loop - declare - Position: IVector2 := Game.Bosses(Me).Position; - begin - while Boss_Can_Stand_Here(Game, Position, Me) loop - Position := Position + Direction_Vector(Dir); - if Within_Map(Game, Position) and then Game.Bosses(Me).Path(Position.Y, Position.X) = Current - 1 then - Game.Bosses(Me).Position := Position; - exit Search; - end if; - end loop; - end; - end loop Search; - end; - Game.Bosses(Me).Attack_Cooldown := SHREK_ATTACK_COOLDOWN; - else - Game.Bosses(Me).Attack_Cooldown := Game.Bosses(Me).Attack_Cooldown - 1; - end if; - if Inside_Of_Rect(Game.Bosses(Me).Position, Game.Bosses(Me).Size, Game.Player.Position) then - Game.Player.Dead := True; - end if; - if Game.Bosses(Me).Health < 1.0 then - Game.Bosses(Me).Health := Game.Bosses(Me).Health + SHREK_TURN_REGENERATION; - end if; - end if; - end loop; - + Game_Explosions_Turn(Game); + Game_Player_Turn(Game, Dir); + Game_Bombs_Turn(Game); + Game_Items_Turn(Game); + Game_Bosses_Turn(Game); Game.Duration_Of_Last_Turn := Get_Time - Start_Of_Turn; end; end if; @@ -974,14 +1055,18 @@ procedure Game is Size: constant Vector2 := To_Vector2(Boss.Size)*Cell_Size; begin if not Boss.Dead then - Draw_Rectangle_V(Position, Cell_Size*To_Vector2(Boss.Size), Palette_RGB(Boss.Background)); case Boss.Behavior is - when Shrek => + when Gnome => + declare + GNOME_SIZE: constant C_Float := 0.7; + begin + Draw_Rectangle_V(Position + Cell_Size*0.5 - Cell_Size*GNOME_SIZE*0.5, Cell_Size*GNOME_SIZE, Palette_RGB(Boss.Background)); + end; + when Shrek | Urmom => + Draw_Rectangle_V(Position, Cell_Size*To_Vector2(Boss.Size), Palette_RGB(Boss.Background)); Health_Bar(Position, Size, C_Float(Boss.Health)); - when Urmom => - null; + Draw_Number(Position, Size, Boss.Attack_Cooldown, (A => 255, others => 0)); end case; - Draw_Number(Position, Size, Boss.Attack_Cooldown, (A => 255, others => 0)); end if; end; end loop; @@ -994,7 +1079,9 @@ procedure Game is Palette_Editor_Choice: Palette := Palette'First; Palette_Editor_Selected: Boolean := False; Palette_Editor_Component: HSV_Comp := Hue; + begin + Reset(Gen); Load_Colors("colors.txt"); Load_Game_From_File("map.txt", Game, True); Game_Save_Checkpoint(Game); @@ -1088,6 +1175,19 @@ begin Game_Player(Game); Game_Bosses(Game); Game_Bombs(Game); + if DEVELOPMENT then + if Is_Key_Down(KEY_P) then + for Row in Game.Map'Range(1) loop + for Column in Game.Map'Range(2) loop + declare + Position: constant Vector2 := To_Vector2((Column, Row))*Cell_Size; + begin + Draw_Number((Column, Row), Game.Bosses(1).Path(Row, Column), (A => 255, others => 0)); + end; + end loop; + end loop; + end if; + end if; End_Mode2D; Game_Hud(Game); @@ -1153,6 +1253,8 @@ end; -- 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: Gnome should have triangular hats in the form of keys +-- And key must become triangles intead of circles -- TODO: Player pushing bombs mechanic -- TODO: animate key when you pick it up -- Smoothly move it into the HUD. diff --git a/map.txt b/map.txt index 1cb95fd..892e9b6 100644 --- a/map.txt +++ b/map.txt @@ -20,8 +20,8 @@ #####.##### #...................# #.# #...................# #.# #...................# - #.# #########...######### - #####.##### #...# + #.# ##################### + #####.##### #.@.# #.........# #...# #.........# #...# #.........# #...# @@ -41,26 +41,26 @@ #...# #...# ######################## #...# - ###...##........##...### #...# - ###...##........##...### #...# - #......=...............# #...# - #...*..=...........*...# #...# - #......=...............# #...# - ###...##........##...### #...# - ###===##........##...### #...# -#################################......................# #...# -.........................................M.............# #...# -.......................................................##############################...# -......................................................................................!.# -........................................................................................# -.......................................................################################## -.......................................................# -#################################......................# + ###===##........##===### #...# + ###=.=##........##=.=### #...# + #===.===........===.===# #...# + #=..*..=........=..*..=# #...# + #===.===........===.===# #...# + ###=.=##........##=.=### #...# ################# + ###===##........##===### #...# #......G........# +#################################......................# #...# #...............# +.........................................M.............# #...# #...............# +.......................................................##############################...####################...............# +......................................................................................................................G....# +......................................................................................!....................................# +.......................................................#####################################################...............# +.......................................................# #...............# +#################################......................# #...............# + ###===##........##===### #..G............# + ###=.=##........##=.=### ################# + #===.===........===.===# + #=..*..=........=..*..=# + #===.===........===.===# + ###=.=##........##=.=### ###===##........##===### - ###...##........##=.=### - #......=........===.===# - #...*..=........=..*..=# - #......=........===.===# - ###...##........##=.=### - ###...##........##===### ######################## diff --git a/test.adb b/test.adb index c74e193..59b35f3 100644 --- a/test.adb +++ b/test.adb @@ -4,24 +4,14 @@ with Ada.Strings.Fixed; use Ada.Strings.Fixed; with Ada.Strings; use Ada.Strings; with Ada.Containers.Vectors; with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; -with Queue; +with Ada.Numerics.Discrete_Random; procedure Test is - package String_Queue is new Queue(Item => Unbounded_String); - use String_Queue; - Q: String_Queue.Queue; - X: Unbounded_String; + type Direction is (Left, Right, Up, Down); + type Array_Direction is array(Direction) of Integer; + -- type Direction is range 1..10; begin - for Index in 1..16 loop - Enqueue(Q, To_Unbounded_String(Index'Image)); - end loop; - while Dequeue(Q, X) loop - null; - end loop; - for Index in 32..42 loop - Enqueue(Q, To_Unbounded_String(Index'Image)); - end loop; - while Dequeue(Q, X) loop - Put_Line(To_String(X)); - end loop; + Put_Line(Direction'First'Image); + Put_Line(Direction'Last'Image); + Put_Line(Array_Direction'Length'Image); end; -- cgit v1.2.3