Description
A Gnoga program to play the classic snake game, by David Botton.
Source code
snake.ads
------------------------------------------------------------------------------
-- --
-- Snake Game Demo --
-- (c) 2014 - David Botton --
-- License GPLv3 --
-- --
-- --
-- A simple demonstration of using Gnoga for 2D game development. --
-- --
------------------------------------------------------------------------------
with Gnoga;
package Snake is
Title : constant Gnoga.String := "Sparky the Snake";
-- Game Display
Display_Width : constant := 400;
Display_Height : constant := 400;
-- Snake Parameters
Initial_Length : constant := 5;
Segment_Size : constant := 10;
end Snake;
snake-connection.ads
with Ada.Numerics.Discrete_Random;
with Ada.Containers.Vectors;
with Gnoga.Types;
with Gnoga.Gui.View;
with Gnoga.Gui.Element.Common;
with Gnoga.Gui.Element.Canvas;
with Gnoga.Gui.Element.Canvas.Context_2D;
package body Snake.Connection is
use Gnoga;
use Gnoga.Types;
use Gnoga.Gui.Element.Common;
use Gnoga.Gui.View;
use Gnoga.Gui.Element.Canvas;
use Gnoga.Gui.Element.Canvas.Context_2D;
use all type Gnoga.String;
--------------------
-- Display_Splash --
--------------------
procedure Display_Splash (Main_Window : in out Window_Type'Class) is
Display : DIV_Type;
begin
Display.Create
(Main_Window,
"<H1>" & Snake.Title & "</H1>" & "<br />" & "<p>Use your kebyoard to move Sparky to pick up batteries.</p>" &
"<i>Be careful...</i><br />" & "If sparky hits his tail he electrocutes" & " himself to <b>death!!</b>" &
"<br /><br />" & "Use the arrow keys or a,w,s,d for direction keys.<br/><br/>");
Main_Window.Set_View (Display);
Display.Text_Alignment (Gnoga.Gui.Element.Center);
for i in 1 .. 15 loop
Display.Put (" * ");
delay 0.3;
end loop;
Display.Visible (False);
exception
when others =>
Gnoga.Log ("Exception in Display_Splash");
end Display_Splash;
type Snake_Direction_Type is (Left, Right, Up, Down);
package Snake_Arrays is new Ada.Containers.Vectors (Natural, Point_Type);
function New_Food return Point_Type;
function New_Food return Point_Type is
subtype X_Range is Integer range 0 .. (Display_Width / Segment_Size) - 1;
subtype Y_Range is Integer range 0 .. (Display_Height / Segment_Size) - 1;
package Random_X is new Ada.Numerics.Discrete_Random (X_Range);
package Random_Y is new Ada.Numerics.Discrete_Random (Y_Range);
X_Gen : Random_X.Generator;
Y_Gen : Random_Y.Generator;
Food_Cell : Point_Type;
begin
Random_X.Reset (X_Gen);
Random_Y.Reset (Y_Gen);
Food_Cell := (Random_X.Random (X_Gen), Random_Y.Random (Y_Gen));
return Food_Cell;
exception
when others =>
Gnoga.Log ("Exception in New_Food");
return Food_Cell;
end New_Food;
type App_Data is new Connection_Data_Type with record
Main_Window : Pointer_To_Window_Class;
Background : View_Type;
Display : Canvas_Type;
Score : Integer := 0;
Snake : Snake_Arrays.Vector;
Snake_Direction : Snake_Direction_Type := Right;
Food : Point_Type := New_Food;
end record;
type App_Access is access all App_Data;
procedure Paint
(Context : in out Context_2D_Type;
App : in App_Access;
Game_Over : out Boolean);
----------------
-- Start_Game --
----------------
procedure Start_Game (Main_Window : in out Window_Type'Class) is
App : constant App_Access := App_Access (Main_Window.Connection_Data);
Context : Context_2D_Type;
Game_Over : Boolean;
begin
App.Main_Window.On_Key_Down_Handler (On_Key_Down'Access);
App.Background.Create (Main_Window);
App.Background.Background_Color ("orange");
App.Display.Create (App.Background, Display_Width, Display_Height);
App.Display.Display ("block");
App.Display.Margin ("auto", "auto", "auto", "auto");
App.Display.Border (Width => "thin");
App.Display.Border_Radius ("10px");
App.Display.Background_Color ("white");
App.Display.Shadow (Horizontal_Position => "3px", Vertical_Position => "3px", Blur => "5px");
-- Initialize Snake
for i in reverse 0 .. Initial_Length - 1 loop
App.Snake.Append (Point_Type'(i, 0));
end loop;
Context.Get_Drawing_Context_2D (App.Display);
loop
if Main_Window.Connection_Data = null then
exit;
end if;
Paint (Context, App, Game_Over);
if Game_Over then
exit;
end if;
delay 0.1;
end loop;
exception
when others =>
Gnoga.Log ("Exception in Start_Game");
end Start_Game;
procedure On_Key_Down
(Object : in out Base_Type'Class;
Key : in Keyboard_Event_Record)
is
App : constant App_Access := App_Access (Object.Connection_Data);
begin
if Key.Key_Code = 38 or Key.Key_Code = Character'Pos ('W') then
App.Snake_Direction := Up;
elsif Key.Key_Code = 37 or Key.Key_Code = Character'Pos ('A') then
App.Snake_Direction := Left;
elsif Key.Key_Code = 39 or Key.Key_Code = Character'Pos ('D') then
App.Snake_Direction := Right;
elsif Key.Key_Code = 40 or Key.Key_Code = Character'Pos ('S') then
App.Snake_Direction := Down;
end if;
exception
when others =>
Gnoga.Log ("Exception in On_Key_Down");
end On_Key_Down;
procedure Paint
(Context : in out Context_2D_Type;
App : in App_Access;
Game_Over : out Boolean)
is
procedure Draw_Segment (Cell : Point_Type);
procedure Draw_Segment (Cell : Point_Type) is
Cell_Rectangle : constant Rectangle_Type :=
(Cell.X * Segment_Size, Cell.Y * Segment_Size, Segment_Size, Segment_Size);
begin
Context.Fill_Rectangle (Cell_Rectangle);
end Draw_Segment;
function Self_Collision
(Cell : Point_Type)
return Boolean;
function Self_Collision
(Cell : Point_Type)
return Boolean
is
begin
for i in App.Snake.First_Index .. App.Snake.Last_Index loop
declare
Current : constant Point_Type := App.Snake.Element (i);
begin
if Current.X = Cell.X and Current.Y = Cell.Y then
return True;
end if;
end;
end loop;
return False;
end Self_Collision;
function Food_Collision
(Cell : Point_Type)
return Boolean;
function Food_Collision
(Cell : Point_Type)
return Boolean
is
begin
if Cell.X = App.Food.X and Cell.Y = App.Food.Y then
return True;
else
return False;
end if;
end Food_Collision;
begin
Game_Over := False;
-- Snake Move Code
declare
Head_Cell : Point_Type := App.Snake.Element (0);
begin
case App.Snake_Direction is
when Right =>
Head_Cell.X := Head_Cell.X + 1;
when Left =>
Head_Cell.X := Head_Cell.X - 1;
when Up =>
Head_Cell.Y := Head_Cell.Y - 1;
when Down =>
Head_Cell.Y := Head_Cell.Y + 1;
end case;
if Head_Cell.X < 0 or Head_Cell.X * Segment_Size >= Display_Width or Head_Cell.Y < 0 or
Head_Cell.Y * Segment_Size >= Display_Height or Self_Collision (Head_Cell)
then
Context.Fill_Color ("red");
Context.Font (Height => "20px");
Context.Fill_Text ("GAME OVER", 30, 30);
Game_Over := True;
else
App.Snake.Prepend (Head_Cell);
-- Snake Draw
for i in App.Snake.First_Index .. App.Snake.Last_Index loop
Context.Fill_Color ("purple");
Draw_Segment (App.Snake.Element (i));
end loop;
if Food_Collision (Head_Cell) then
-- clear old score
Context.Fill_Color ("white");
Context.Font (Height => "12px");
Context.Fill_Text ("Score :" & Image (App.Score), 5, Display_Height - 15);
App.Score := App.Score + 10;
App.Food := New_Food;
else
Context.Fill_Color ("white");
Draw_Segment (App.Snake.Element (App.Snake.Last_Index));
App.Snake.Delete_Last;
end if;
Context.Fill_Color ("brown");
Draw_Segment (App.Food);
end if;
end;
Context.Fill_Color ("green");
Context.Font (Height => "12px");
Context.Fill_Text ("Score :" & Image (App.Score, Prefix => ' '), 5, Display_Height - 15);
exception
when others =>
Gnoga.Log ("Exception in Paint");
end Paint;
------------------------
-- On_Connect_Default --
------------------------
procedure On_Connect_Default
(Main_Window : in out Window_Type'Class;
Connection : access Connection_Holder_Type)
is
pragma Unreferenced (Connection);
App : constant App_Access := new App_Data;
begin
Main_Window.Connection_Data (App);
App.Main_Window := Main_Window'Unchecked_Access;
Display_Splash (Main_Window);
if Main_Window.Connection_Data /= null then
-- If Connection_Data is null then the browser was closed before
-- start of game.
Start_Game (Main_Window);
end if;
exception
when others =>
Gnoga.Log ("Exception in snake");
end On_Connect_Default;
begin
On_Connect_Handler (Event => Snake.Connection.On_Connect_Default'Unrestricted_Access, Path => "default");
end Snake.Connection;
snake-main.adb
with Snake;
with Gnoga.Application.Multi_Connect;
with Snake.Connection;
procedure Snake.Main is
use Gnoga;
use Gnoga.Application.Multi_Connect;
begin
Application.Title (Snake.Title);
Application.HTML_On_Close ("Application disconnected.");
Initialize;
Message_Loop;
end Snake.Main;
Project file
with "settings.gpr";
with "gnoga.gpr";
project Snake is
for Object_Dir use Settings.Obj_Dir;
for Exec_Dir use Settings.Exe_Dir;
for Main use ("snake-main.adb");
for Create_Missing_Dirs use Settings'Create_Missing_Dirs;
package Builder is
for Executable ("snake-main.adb") use "snake";
end Builder;
package Compiler is
for Default_Switches ("Ada") use Settings.Compiler'Default_Switches ("Ada") & "-gnatyN";
end Compiler;
package Binder renames Settings.Binder;
package Linker renames Settings.Linker;
package Pretty_Printer renames Settings.Pretty_Printer;
end Snake;