ascii3D

Retro racing game

Introduction

Peter's short but ingenious program (now available as a web version to play online) came fifth equal in the recent mini-competition organised by PascalGameDevelopment.com. The games were restricted to input by keyboard only using eight or fewer buttons, with bonus points for each unused button. Only one month was allowed for game development. You can see here the judges' feedback on the program.

Peter's instructions are simple:

Left and right arrow keys to move left and right.
Get across the finish line as soon as possible.
Get the magenta circles for boosts. Avoid going off the track, which will slow you down.
Avoid crashing into the other cars as your speed will go back to zero.

Each of these screenshots of the game in action has a brief description underneath.

Opening Screen

Opening Screen

The screenshot of the opening screen shows the instructions.

Approaching a boost circle

Approaching a boost circle

Your car is cornering left, approaching a boost and catching up with the car ahead.

Passing a tree

Passing a tree

The tree you are passing is close to you so is drawn at its maximum size.

Finishing

Finishing

Your car is approaching the finish. You are also about to receive a boost.

The centre of your car stays where it is. The front and back end are displaced when you drive round a bend. You have the impression of movement from the way the other car, the magenta boost circle and the trees are drawn closer to your car while increasing in size as they do so. For example, the magenta boost changes from a full stop through a lower case o to an upper case O. Also, the track is narrower in the distance to provide another 3D effect.

Peter uses only 11 characters (including the space) to represent the cars, trees, track, boost and finish line! He does, however, indulge in the display of 13 colours.

You can download the code in ascii3D.txt. You need no additional files to run the game. (Lazarus already has the crt unit). We inserted the line of code sleep(100); just before the end of the repeat loop so that the flickering was less disturbing and we had time to see clearly the changing appearance of the trees, boost circles and the other cars as we became closer to them.

The Program

program ascii3D;
{
    Copyright (c) 2011 Peter Hearnshaw

    Licensed under the Apache License, Version 2.0 (the "License"); you may not
    use this file except in compliance with the License, as described at
    http://www.apache.org/licenses/ and http://www.pp4s.co.uk/licenses/
}
uses
  Classes, SysUtils, crt, math, windows;

var
  xpos, i, a, sidewaysMovement, middleX, displayMessageTimer, finishX, finishX2,
    curve, carShift, boostCircleY, boostCircleX, otherCarY, otherCarX, finalScore,
    startTimeInt, roadX1, roadX2, treeY, treeX, speed, throughTrack, finishY : integer;
  otherCarYReal : real;
  treeLeft, otherCarLeft : boolean;
  displayMessage : string;
  c : char;
  curveArrPoints : array [1 .. 28] of integer = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 12, 12,
                                                 12, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
  totalRoadArr : array [1 .. 20, 1 .. 2] of integer;
                //80 BY 25

const
  trackWidth = 18;

function processTime : integer;
var
  nowTime : tdatetime;
  Strtime: string;
  hrsStr, minsStr, secsStr : string;
begin
  nowTime:= Now;
  StrTime := StringReplace(timetostr(nowTime), ':', '',[rfReplaceAll, rfIgnoreCase]);
  hrsStr := LeftStr (StrTime, 2);
  delete (StrTime, 1, 2);
  minsStr := LeftStr (Strtime, 2);
  delete (StrTime, 1, 2);
  secsStr := LeftStr (StrTime, 2);
  result := strtoint(hrsStr) * 60 * 60 + strtoint(minsStr) * 60 + strtoint(secsStr);
end;

procedure resetVariables;
begin
  startTimeInt := processTime;
  xpos := 32;
  sidewaysMovement := 0;
  speed := 0;
  throughTrack := 0;
  finishY := 0;
  boostCircleY := 0;
  otherCarY := 0;
  treeY := 0;
  displayMessage := '';
end;

procedure startScreen;
var
  escape : boolean;
begin
  textbackground(white);
  clrscr;
  textcolor(LightRed);
  gotoXY(22, 1);
  writeln('000     00000  000000 000       000  ');
  gotoXY(22, 2);
  writeln('00 00   00000   0000  00 00    00 00 ');
  gotoXY(22, 3);
  writeln('00  00  00       00   00  00  00   00');
  gotoXY(22, 4);
  writeln('00  00  00       00   00  00  00   00');
  gotoXY(22, 5);
  writeln('00 00   0000     00   00 00   00   00');
  gotoXY(22, 6);
  writeln('000     0000     00   000     00   00');
  gotoXY(22, 7);
  writeln('000     00       00   000     00   00');
  gotoXY(22, 8);
  writeln('00 00   00       00   00 00   00   00');
  gotoXY(22, 9);
  writeln('00  00  00000    00   00  00   00 00 ');
  gotoXY(22, 10);
  writeln('00  00  00000    00   00  00    000  ');
  gotoXY(33, 12);
  textcolor(red);
  writeln('RACING PROGRAM');

  gotoXY(28, 15);
  write('GET THE BOOSTS: ');
  textColor(lightmagenta);
  write('O');
  gotoXY(28, 18);
  textcolor(red);
  write('AVOID THE CARS: ');
  textbackground(cyan);
  textColor(white);
  gotoXY(45, 17);
  write('/  \');
  gotoXY(45, 18);
  write('[  ]');
  gotoXY(45, 19);
  write('____');
  textColor(red);
  textbackground(lightgray);
  gotoXY(19, 21);
  write('JUST USE LEFT AND RIGHT ARROW KEYS TO MOVE');
  gotoXY(18, 22);
  write('CAR SPEED IS AUTOMATIC, NO NEED TO USE UP KEY');
  gotoXY(22, 23);
  write('CROSS THE FINISH AS SOON AS POSSIBLE');

  gotoXY(28, 25);
  write('PRESS UP TO START PLAYING');
  escape := false;
  repeat
    sleep(15);
    if (Keypressed) then
      begin
        c := readkey;
        case c of
          #72 : escape := true;
        end;
      end;
  until escape = true;
  resetVariables;
end;

procedure doOtherCar;
begin
  if (random(15) = 3) and (otherCarY = 0) then
    begin
      otherCarY := 11;
      otherCarYReal := 11;
      otherCarLeft := false;
      if (random(2) = 1) then
        otherCarLeft := true;
    end;
  if (otherCarY > 0) then
    begin
      otherCarYReal := otherCarYReal + (speed / 3900) + (otherCarY / 40);
      otherCarY := round(otherCarYReal);
      if (otherCarY >= 25) then
        begin
          otherCarY := 0;
          otherCarYReal := 0;
        end;
    end;
  if (otherCarY > 0) then
    begin
      if (otherCarLeft = true) then
        otherCarX := -1 + round(2 * (otherCarY - 5) / 5) + xpos -
                                ((otherCarY - 11) - 7) - sidewaysMovement +
                                round(sidewaysMovement * sin((otherCarY - 11) * 7 * 0.0174));
      if (otherCarLeft = false) then
        otherCarX := round(-2 * (otherCarY - 5) / 5) + xpos +
                           trackWidth + ((otherCarY - 11) - 7) -
                           sidewaysMovement + round(sidewaysMovement * sin((otherCarY - 11) * 7 * 0.0174));
    end;
end;

procedure doGold;
begin
  if (random(15) = 3) and (boostCircleY = 0) then
    boostCircleY := 10;
  if (boostCircleY > 0) then
    begin
      boostCircleY := boostCircleY + 1;
      if (boostCircleY = 25) then
        boostCircleY := 0;
    end;
  //display the gold
  if (boostCircleY > 0)then
    begin
      boostCircleX := round((xpos - ((boostCircleY - 10) - 7) - sidewaysMovement +
                            round(sidewaysMovement * sin((boostCircleY - 10) * 7 * 0.0174)) +
                            xpos + 18 + ((boostCircleY - 10) -7) - sidewaysMovement +
                            round(sidewaysMovement * sin((boostCircleY - 10) * 7 * 0.0174))) / 2);
    end;
end;

procedure doTree;
var
  spacingFromRoad : integer;
begin
  if (random(8) = 3) and (treeY = 0) then
    begin
      treeY := 11;
      treeLeft := false;
      if (random(2) = 1) then
        treeLeft := true;
    end;
  if (treeY > 0) then
    begin
     treeY := treeY + round((treeY - 10) / 5) + 1;
     if (treeY = 25) then
       treeY := 0;
    end;
  if (treeY > 0) then
    begin
      spacingFromRoad := 4 + round(treeY - 10); //move tree 4 points from roadside coord.
                                                //Also move further away from road as tree moves closer (perspective)
      if (treeLeft = true) then
        treeX := xpos - spacingFromRoad - ((treeY - 10) - 7) - sidewaysMovement +
                 round(sidewaysMovement * sin((treeY - 10) * 7 * 0.0174));
      if (treeLeft = false) then
        treeX := xpos + spacingFromRoad + trackWidth + ((treeY - 10) - 7) - sidewaysMovement +
        round(sidewaysMovement * sin((treeY - 10) * 7 * 0.0174));
        textcolor(lightgray);
    end;
end;

procedure finishScreen;
var
  escape : boolean;
begin
  finalScore := processTime - startTimeInt;
  textbackground(white);
  clrscr;
  textcolor(black);
  writeln('ACROSS THE FINISH LINE...');
  writeln('IN ' + inttostr(finalScore) + ' SECONDS');
  writeln;
  write('PRESS UP TO RETURN TO THE MAIN MENU');
  escape := false;
  repeat
    sleep(15);
    if (Keypressed) then
      begin
        c := readkey;
        case c of
          #72 : escape := true;
        end;
      end;
  until escape = true;
  startScreen;
end;

procedure doFinishLine;
begin
  inc(finishY);
  if(finishY > 22) then
    begin
      finishScreen;
    end;
  finishX := xpos -((finishY - 11) - 7) - sidewaysMovement +
              round(sidewaysMovement * sin((finishY - 11) * 7 * 0.0174));
  finishX2 := xpos + trackWidth + ((finishY - 11) - 7) - sidewaysMovement +
               round(sidewaysMovement * sin((finishY - 11) * 7 * 0.0174));
end;

begin
  randomize;
  startScreen;
  repeat
    doGold;
    doTree;
    doOtherCar;

    inc(throughTrack);
    //if (throughTrack > 550) then //how long the track is
    if (throughTrack > 50) then //how long the track is
      begin
        if (finishY = 0) then
          finishY := 10;
        doFinishLine;
      end;

     //work out the road
    for i := 11 to 25 do  //for each line from horizon to bottom of screen
      begin               //roughly sine shape from 0 to 90 degrs
        roadX1 := xpos -((i - 11) - 7) - sidewaysMovement +
                  round(sidewaysMovement * sin((i - 11) * 7 * 0.0174));
        roadX2 := xpos + trackWidth + ((i - 11) - 7) - sidewaysMovement +
                  round(sidewaysMovement * sin((i - 11) * 7 * 0.0174));
        totalRoadArr[i - 10][1] := roadX1;
        totalRoadArr[i - 10][2] := roadX2;
      end;
    if (sidewaysMovement = 0) then
      begin
        case random(35) of
          1 : sidewaysMovement := 1;
          2 : sidewaysMovement := -1;
        end;
        curve := 0;
      end;
    if (sidewaysMovement > 0) then
      begin
        inc(curve);
        sidewaysMovement := curveArrPoints[curve];
      end;
    if (sidewaysMovement < 0) then
      begin
        dec(curve);
        sidewaysMovement := -curveArrPoints[-curve];
      end;
    if (sidewaysMovement > 0) then
      xpos := xpos - 1;
    if (sidewaysMovement < 0) then
      xpos := xpos + 1;

    if (speed > 70) then
      begin
        if (roadX2 < 40) or (roadX1 > 40) then
          speed := speed - 3;
      end;
    if (speed < 100) then
      begin
         speed := speed + 2;
      end;
    if (speed > 100) then
      begin
         speed := round(speed * 0.99);
      end;

    while (Keypressed) do
      begin
        c := readkey;
        case c of
          #75 : xpos := xpos + 3;
          #77 : xpos := xpos - 3;
        end;
      end;

    if (sidewaysMovement > 4) then
      carShift := 1;
    if (sidewaysMovement < -4) then
      carShift := -1;
    if(sidewaysMovement = 0) then
      carShift := 0;

    if (boostCircleY > 22) and ((boostCircleX + 5) > 40) and ((boostCircleX - 5) < 40) then
      begin
        boostCircleY := 0;
        displayMessage := 'BOOST';
        displayMessageTimer := 15;
      end;
    if (otherCarY > 21) and ((otherCarX + 3) > 40) and ((otherCarX - 3) < 40) then
      begin
        //crashed
        speed := 0;
        textbackground(Blue);
        textcolor(white);
        gotoXY(37, 8);
        writeln('CRASH');
        sleep(800);
        otherCarY := 0;
        displayMessage := '';
      end;
    if (displayMessage = 'BOOST') then
      begin
        dec(displayMessageTimer);
        if (displayMessageTimer = 0) then
          begin
             displayMessage := '';
             displayMessageTimer := 0;
          end;
        speed := speed + round((200 - speed) / 25);
      end;

    //VISUAL STUFF
    TextBackground(Green);
    clrscr;  //makes whole screen green
    for a := 1 to 9 do //blue sky
      begin
        gotoXY(1, a);
        TextBackground(blue);
        ClrEol;
      end;
    textcolor(white);
    gotoXY(1, 1);
    writeln('TIME ELAPSED: ' + inttostr(processTime - startTimeInt) + ' SECONDS');
    gotoXY(28, 1);
    writeln('SPEED: ' + inttostr(speed));
    gotoXY(47, 1);
    middleX := round((totalRoadArr[15][1] + totalRoadArr[15][2]) / 2);
    if (middleX > 70) or (middleX < 10) then
      begin
        speed := 0;
        textbackground(Blue);
        textcolor(white);
        gotoXY(33, 8);
        writeln('CRASHED OFF ROAD');
        sleep(800);
        otherCarY := 0;
        displayMessage := '';
        xpos := 32;
        sidewaysMovement := 0;
        displayMessage := 'CRASHED OFF ROAD';
      end;

    //Sets horizon line
    TextBackground(White);
    gotoXY(1, 10);
    ClrEol;
    //TRACK
    for i := 11 to 25 do  //for each line from horizon to bottom of screen
      begin
        TextBackground(LightGray);
        if (totalRoadArr[i - 10][1] < 81) and (totalRoadArr[i - 10][1] > 0) then
          begin
            gotoXY(totalRoadArr[i - 10][1], i);
            Write(' ');
            TextBackground(brown);
            clrEol;
          end
        else
          begin
            gotoXY(1, i);
          end;
        TextBackground(brown);
        clrEol;
        if (totalRoadArr[i - 10][2] < 81) and (totalRoadArr[i - 10][2] > 0) then
          begin
            TextBackground(LightGray);
            gotoXY(totalRoadArr[i - 10][2], i);
            Write(' ');
            TextBackground(green);
            clrEol;
          end;
      end;
    //Draw Car
    TextBackground(lightred);
    textcolor(white);
    gotoXY(40 - carShift, 23);
    write('/  \');
    gotoXY(40, 24);
    write('[  ]');
    gotoXY(40 + carShift, 25);
    write('____');
    //Finish Line
    if (finishX > 0)then
      begin
        gotoXY(finishX, finishY);
        textbackground(Cyan);
        textcolor(yellow);
        for i := finishX + 1 to finishX2 - 1 do
          begin
            gotoXY(i, finishY);
            writeln('|');
          end;
      end;
    //Messages
    if (displayMessage = 'BOOST') then
      begin
        textbackground(Blue);
        textcolor(white);
        gotoXY(37, 8);
        writeln('BOOST');
      end;
    //Draw Tree
    if (treeY > 0) and (treeX < 74) and (treeX > 6) then
      begin
        textbackground(green);
        textcolor(lightgreen);
        if (treeY > 18) then
          begin
            gotoXY(treeX - 3, treeY);
            write('  /\  ');
            gotoXY(treeX - 3, treeY + 1);
            write(' /  \ ');
            gotoXY(treeX - 3, treeY + 2);
            write('/____\');
            textcolor(black);
            if (treeY + 3 < 25) then
              begin
                gotoXY(treeX - 3, treeY + 3);
                write('  II  ');
              end;
           end
        else if (treeY < 15) then
          begin
             gotoXY(treeX - 1, treeY);
             write('/\');
          end
        else
          begin
            gotoXY(treeX - 2, treeY);
            write(' /\ ');
            gotoXY(treeX - 2, treeY + 1);
            write('/  \');
            textcolor(black);
            gotoXY(treeX - 2, treeY + 2);
            write(' II ');
          end;
      end;
    //Do BOOSTS
    if (boostCircleY > 0) then
      begin
        gotoXY(boostCircleX, boostCircleY);
        TextBackground(brown);
        textcolor(LightMagenta);
        if (boostCircleY > 18) then  //if very close make gold large
          begin
            write('O');
          end
        else if (boostCircleY < 14) then  //if far away make gold small
          begin
            write('.');
          end
        else         //if in between make gold medium
          begin
            write('o');
          end;
      end;
    //Do Other car
    if (otherCarY > 0) then
      begin
        TextBackground(Cyan);
        textcolor(BLUE);
        if (otherCarY > 23) then
          begin
           gotoXY(otherCarX, otherCarY);
           write('/  \');
           gotoXY(otherCarX, otherCarY + 1);
           write('[  ]');
          end
        else if (otherCarY > 18) then
           begin
            gotoXY(otherCarX, otherCarY);
            write('/  \');
            gotoXY(otherCarX, otherCarY + 1);
            write('[  ]');
            gotoXY(otherCarX, otherCarY + 2);
            write('____');
           end
        else if (otherCarY > 14) then
          begin
            gotoXY(otherCarX, otherCarY);
            write('/\');
            gotoXY(otherCarX, otherCarY + 1);
            write('__');
          end
        else if (otherCarY < 0) then
          begin
            gotoXY(otherCarX, otherCarY);
            write('/\');
          end;
      end;
    gotoXY(1, 1);
    if (displayMessage <> 'CRASHED OFF ROAD') then
      sleep(200 - speed);
    if (displayMessage = 'CRASHED OFF ROAD') then
      displayMessage := '';
  until 1 = 2;
end.
Programming - a skill for life!

Fourteen programs (with five web versions) including 3D-Driving, GASP and Knowledge by Peter Hearnshaw