>
Fortran has been around for ages. It began around late 1953 and early 1954 as a revolution in
"automatic programming" in a day when code was written using mnemonic devices of raw assembly
instructions [1]. To most it was believed that the inginuity of humanity was required to create
efficient software. But after pulling all-nighters in the Langdon Hotel in spring 1956,
John Backus and others managed to produce a working compiler in early 1957. About which John remarked
[...] we were often astonished at the surprising transformations in the
indexing operations and in the arrangement of the computation which the compiler made,
changes which we would not have thought to make as programmers ourselves [...]. Transfers
of control appeared which corresponded to no source statement, expressions were radically arranged
, and the same DO statement might produce no instructions in the
object program in one context, and in another it would produce many instructions in different places in the program.
They had shown a compiler could produce better code than an expert human. Fortran in 1957 worked on punchcards, literally
a piece of card with some holes in it to represent the source code data. Key to the language design was the use of
an algebraic syntax, the clue is in the name FORmula TRANslation. But also the use of input and output statments,
integers and floating point numbers, mathematical operators like \(+, -, =, >, <\),
arrays a(i, j), subroutines and more. Since the 50's Fortran has
gone through many revisions to add new features such as character data in the
77 standard, control flow structures and modules in the 90 standard,
parallel computing built-ins in the 95 standard and even object-oriented programming in the
03 standard (there is even talk of C++ like templates in future standards [2-3]) with the 2023 standard as
the most recent [4].
In spite of its continued development it has the reputation of a dated, obsolete
language, used by perhaps only a handful of old developers. And yet Fortran is used extensively in many scientific contexts,
in part due to inertia. If very efficient and complex code has solved a problem, why re-implement it again?
One example domain is Quantum chemisty and solid state
physics, where many CPU and GPU based software is still maintained in Fortran [5]. Another example are the
excellent linear algebra routines and interfaces written in Fortran such as BLAS/LAPACK [6, 7] which have
been the workhorse of many applications, including especially a lot of machine learning (although now of course
new CPU/GPU libraries have been developed).
In short Fortran was built for numerical computing, and it has been very successful at it. So how
do we get to a game in such a language? It turns out that Prof. Anthony Stone (Cambridge)
and Aleksandar Donev maintained Fortran 2003 bindings for OpenGL [8] stemming from Fortran 90 standard bindings for OpenGL, from William F. Mitchell [9].
Amazingly if you download the source code, at least on Ubuntu 22.04, and run make
you will be greated with a series of reasonably complex demos. If we can draw stuff, then we can make a game! Let's do something really simple, fortranBird - a
Flappy Bird clone.
To get started, Fortran works like most other compiled languages. The entry point is typically written as
! Fortran is case in-sensistive, the capitalisation is a style choice.
Program main
! NB i is implicitly an Integer, see Implicit None
Do i = 1, 10
Print *, "Hello, World!"
End Do
End Program
Which works as might be expected. The code may be organised into the entrypoint, a set of modules,
functions, and subroutines. For the world geometry and the player we'll want to use some basic drawable unit with collision detection. Quads
are a pretty versatile choice for this purpose so let's define a Quad type in the Quads module.
Module Quads
! A module for drawing untextured Quads
Use opengl_gl
Use opengl_glu
Use opengl_glut
! So that i, j, k, n, ... are not Integer by default.
Implicit None
! NB the module name and anything within it cannot have the same name.
Type, Public :: Quad
Real, Private :: cx, cy, w, h, theta, r, g, b, a
Real, Private :: rot(1:2,1:2)
Real, Private, Dimension(1:2,1) :: bl, tl, tr, br
! Subroutines and Functions of Quad.
Contains
Private
! &, is a line continuation character.
! By default gfortran has a max line length of 132.
Procedure, Public :: draw, set_rotation,&
set_position,&
! => makes init the external alias name for init_quad.
init => init_quad
Procedure, Public :: get_br,&
get_w,&
get_c,&
get_wh
End Type Quad
Contains
! Snipped member Subroutines and Function definitions...
Again just like other languages we have a type with member data and member methods. The init Subroutine takes
position, colour, data etc to setup the quad. The draw method is as follows
Subroutine draw(q)
Class(Quad), Intent(InOut) :: q
! Old OpenGL speak to draw a quad.
Call glBegin(GL_QUADS)
! % is the member access unary operator in Fortran.
Call glColor4f(q%r,q%g,q%b,q%a)
Call glVertex2f(q%bl(1,1), q%bl(2,1))
Call glVertex2f(q%tl(1,1), q%tl(2,1))
Call glVertex2f(q%tr(1,1), q%tr(2,1))
Call glVertex2f(q%br(1,1), q%br(2,1))
Call glEnd()
End Subroutine draw
It is a Subroutine, which means that it is a unit of code that takes inputs and does something to those inputs. There is no explicit return value. Instead we
must give each argument an Intent which may be one or both of In and Out.
Given a Quad initialised with some position and colour data q%draw() will
display it on screen as expected. If the necessary GLUT incantations are used to setup the window.
There are two distinct entity types in the game, obstacles and the bird. For the obstacles we want randomly sized quads at random
positions that move to the left of the screen. With enough spacing for the player to slip through. These obstacles can be stored in an array, and progressively
moved to the left as the game runs. Generation can be done in a Function (in this case as a member of a World type to store obstacles and update them).
Type(Quad) Function generate_obstacle(neighbour, space, height, &
min_width, max_width, min_height, max_height)
Type(Quad), Intent(In ) :: neighbour
Real, Intent(In ) :: space, height,&
min_width, max_width, min_height, max_height
! Data to be used in the Subroutine, any initialisation in these definitions
! makes the data static.
Real, Dimension(1:2) :: c, wh
c = neighbour%get_c()
c(1) = c(1) + space + random()*space + neighbour%get_w()
c(2) = random()*height
wh(1) = random()*(max_width-min_width)+min_width
wh(2) = random()*(max_height-min_height)+min_height
Call generate_obstacle%init(&
(/c(1), c(2)/),&
(/wh(1), wh(2)/),&
0.0,&
colour)
End Function generate_obstacle
Note that the function's return value is
its name generate_obstacle, which we call init on at the end. We could also use a plain
Return statement as well. The next vital entity is the bird. We'll call it a Player and give it
a body that is a Quad and two triangles, one for a beak and a wing.
The rest of the module being utilities for moving etc. To wrap this into a game we need a core gameplay loop. Here it is simple, each frame:
On pressing WASD the bird moves.
The obstacles move to the left at increasing speed.
The bird is pushed downwards.
If the bird collides with an obstacles that is game over.
Most of which is fairly simple to implement in any
language using GLUTs update loop and I/O, and checking bounding box collisions. In the end, adding the play time as a score and text to show it on game over we have a game.
Written in Fortran!
You can find the code and some pre-built binaries on Github https://github.com/JerboaBurrow/FortranBird which likely won't work
on your linux or windows machine, but maybe you'll be lucky