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
The screenshot of the opening screen shows the instructions.

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

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

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.