REM
REM    Emeritus Pong - A clone of the oldschool Pong game
REM    Author: Mateusz Viste
REM    License: GNU/GPL
REM
REM    This program is free software: you can redistribute it and/or modify
REM    it under the terms of the GNU General Public License as published by
REM    the Free Software Foundation, either version 3 of the License, or
REM    (at your option) any later version.
REM
REM    This program is distributed in the hope that it will be useful,
REM    but WITHOUT ANY WARRANTY; without even the implied warranty of
REM    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
REM    GNU General Public License for more details.
REM
REM    You should have received a copy of the GNU General Public License
REM    along with this program.  If not, see <http://www.gnu.org/licenses/>.
REM

#INCLUDE ONCE "vbcompat.bi"      ' Needed for time handling
#IFNDEF __FB_DOS__  ' If not under DOS, include SDL libs
 #INCLUDE ONCE "SDL/SDL.bi"       ' Required for sound support (Linux/Windows)
 #INCLUDE ONCE "SDL/SDL_mixer.bi" ' Required for sound support (Linux/Windows)
 '#INCLUDE ONCE "SDL/SDL_net.bi"   ' This program requires the SDL_net library
#ENDIF
#INCLUDE ONCE "checktsc.bi"      ' CheckTSC()

DECLARE SUB FlushKeyb()
DECLARE SUB RefreshScreen()
DECLARE SUB DrawTable()
DECLARE SUB UpdateBallPosition()
DECLARE SUB ShowScore()
DECLARE SUB InitBall()
DECLARE SUB MainMenu()
DECLARE SUB QuitGame()
DECLARE SUB PlayGame()
DECLARE SUB PlaySound(SndType AS BYTE)
DECLARE SUB Setup()
DECLARE SUB Pause()
DECLARE SUB LoadConfig()
DECLARE SUB Sound(BYVAL freq AS INTEGER, dur AS UINTEGER)
DECLARE SUB CheckPlayersInputs()
DECLARE SUB UpdatePlayersPositions()
DECLARE FUNCTION GenRnd(BYVAL NewGen AS BYTE = 0) AS DOUBLE
DECLARE FUNCTION ReadCFG(CFGfile AS STRING, CFGField AS STRING) AS STRING
DECLARE FUNCTION RDTSC() AS ULONGINT  ' Only used in DOS port
DECLARE FUNCTION uTIMER() AS ULONGINT
'DECLARE SUB InitNetworkSocket()
'DECLARE FUNCTION IPaddressToString(sdlIP AS UINT32) AS STRING

TYPE BallParams
 Speed AS INTEGER
 Angle AS INTEGER
 X AS DOUBLE
 Y AS DOUBLE
END TYPE

CONST Degr2Rad AS DOUBLE = 0.01745329251994330
CONST Mega AS INTEGER = 1000000
DIM SHARED AS UBYTE PalWidth, PalTick, ColorMode, SyncCounter, BallMissed, Score1, Score2, GameMode, Snd, FullScreen, SoundError, EscPressed
DIM SHARED AS INTEGER xRes, yRes, hBorder, vBorder, InitSpeed
DIM SHARED AS DOUBLE AiSpeed, CurPos, CurPos2
DIM SHARED AS ULONGINT LastRefresh, LastMissedBall, LastUpdate, Player1LastMove, Player2LastMove, AiLastDecision, LastClockRefresh, LastAiSpeedChange, LastKeybPolling
DIM SHARED AS BYTE Player1Cmd, Player2Cmd
DIM SHARED AS STRING LastKey, Delimiter
DIM SHARED AS BallParams Ball
DIM SHARED BigNumbers(0 TO 9, 1 TO 5, 1 TO 3) AS BYTE => {{{1,1,1}, {1,0,1}, {1,0,1}, {1,0,1}, {1,1,1}},_
                                                         {{0,0,1}, {0,1,1}, {0,0,1}, {0,0,1}, {0,0,1}},_
                                                         {{1,1,1}, {0,0,1}, {1,1,1}, {1,0,0}, {1,1,1}},_
                                                         {{1,1,1}, {0,0,1}, {1,1,1}, {0,0,1}, {1,1,1}},_
                                                         {{1,0,0}, {1,0,1}, {1,1,1}, {0,0,1}, {0,0,1}},_
                                                         {{1,1,1}, {1,0,0}, {1,1,1}, {0,0,1}, {1,1,1}},_
                                                         {{1,1,1}, {1,0,0}, {1,1,1}, {1,0,1}, {1,1,1}},_
                                                         {{1,1,1}, {0,0,1}, {0,0,1}, {0,0,1}, {0,0,1}},_
                                                         {{1,1,1}, {1,0,1}, {1,1,1}, {1,0,1}, {1,1,1}},_
                                                         {{1,1,1}, {1,0,1}, {1,1,1}, {0,0,1}, {1,1,1}}}


CONST AS STRING pVer = "0.92"
CONST AS STRING pDate = "2008-2012"

#IFNDEF __FB_DOS__
 Delimiter = "/"
 DIM SHARED SndFilePong AS Mix_Chunk POINTER
 DIM SHARED SndFileBounce AS Mix_Chunk POINTER
 DIM SHARED SndFileMissed AS Mix_Chunk POINTER
 SDL_Init(SDL_INIT_AUDIO)          ' SDL sound initialization
 IF (Mix_OpenAudio(44100, AUDIO_S16, 1, 2048))  = -1 THEN
   PRINT "Error: Unable to open the SDL audio mixer! SDL says: " & Mix_GetError()
   SLEEP 1000, 1
   SoundError = 1
 END IF
 SndFilePong = Mix_LoadWAV(EXEPATH & Delimiter & "pong.dat")
 SndFileBounce = Mix_LoadWAV(EXEPATH & Delimiter & "bounce.dat")
 SndFileMissed = Mix_LoadWAV(EXEPATH & Delimiter & "missed.dat")

'  TYPE NetGameProperties
'    State AS BYTE        ' 1 (yes) or 0 (no)
'    RemoteIP AS STRING '  Remote player's IP
'    LocalMode AS STRING  '  "server" or "client"
'    RemotePktCounter AS UINTEGER
'    LocalPktCounter AS UINTEGER
'    LocalPort AS UINTEGER
'    RemotePort AS UINTEGER
'  END TYPE
'  DIM SHARED NetGame AS NetGameProperties
'  DIM SHARED DataPacketRX AS UDPpacket POINTER
'  DIM SHARED DataPacketTX AS UDPpacket POINTER
'  DIM SHARED UdpSrvSocket AS udpsocket
#ENDIF

#IFDEF __FB_DOS__  ' If under DOS, check if RDTSC is supported
 IF CheckTSC() = 0 THEN
   PRINT "Error: Your CPU doesn't support the RDTSC instruction! The DOS version"
   PRINT "       of Emeritus Pong can run on a RDTSC-aware CPU only. The RDTSC"
   PRINT "       instruction is supported on all pentium-compatible processors"
   PRINT "       since 1993."
 END IF
#ENDIF

LoadConfig()
MainMenu()


REM *** END OF THE MAIN PROGRAM ** SUBS AND FUNCTIONS BELOW ***


SUB PlayGame()
 SCREENRES xRes, yRes, 8, 2, FullScreen
 SETMOUSE ,,0      ' Hide the mouse cursor
 SCREENSET 1, 0    ' Displaying 1st video page, but working on the 2nd.
 IF ColorMode = 1 THEN PALETTE 0, &hAA0000    ' (BGR format)
 CLS

 SLEEP 1200, 1
 Ball.X = GenRnd(1)            ' Generate new random numbers
 IF GameMode <> 3 THEN
     Ball.X = xRes * INT(GenRnd*2) ' Randomizing "who plays first"
   ELSE
     Ball.X = 0    ' In "Solo" mode, the ball must come from the left
 END IF
 InitBall()

 LastUpdate = uTIMER
 LastMissedBall = uTIMER

 DO
   CheckPlayersInputs()
   UpdatePlayersPositions()
   IF GameMode = 3 THEN Score2 = Ball.Speed
   IF GameMode = 4 AND ABS(uTIMER - LastClockRefresh) > 3 * Mega THEN
     Score1 = HOUR(NOW)
     Score2 = MINUTE(NOW)
     LastClockRefresh = uTIMER
   END IF
   UpdateBallPosition()
   IF ABS(uTIMER - LastRefresh) > 20000 THEN RefreshScreen()   ' Refresh screen at 50 FPS (1000000 / 20000)
   IF ABS(uTIMER - LastAiSpeedChange) > 5 * Mega THEN
     AiSpeed = 0.2  + GenRnd() * 0.3
     LastAiSpeedChange = uTIMER
   END IF

   SLEEP 1, 1   ' Give some CPU time away

   IF BallMissed = 1 THEN
     RefreshScreen()
     PlaySound(3)
     SLEEP 1000, 1
     IF GameMode <> 4 THEN
       IF Ball.X > xRes \ 2 THEN Score1 += 1 ELSE Score2 += 1
     END IF
     Ball.X = GenRnd(1)  ' The Ball.X is just a fake var. Could be anything.
     InitBall()
     BallMissed = 0
     LastMissedBall = uTIMER
     LastUpdate = uTIMER
     Player1LastMove = uTIMER
     Player2LastMove = uTIMER
   END IF
   IF ABS(uTIMER - LastMissedBall) > 10 * Mega AND Ball.Speed < 12 THEN Ball.Speed += 1: LastMissedBall = uTIMER
   IF ABS(uTIMER - LastKeybPolling) > Mega THEN   ' Poll keyb 3 times in a second
     LastKey = INKEY
     FlushKeyb()
     LastKeybPolling = uTIMER
     IF LastKey = CHR(255) + "k" THEN QuitGame()
   END IF
   #IFDEF __FB_DOS__
     IF Snd = 1 THEN Sound(-1, 0)   ' Checking/processing sound output
   #ENDIF
 LOOP UNTIL EscPressed = 1
 EscPressed = 0
 SLEEP 100, 1
 #IFDEF __FB_DOS__
     IF Snd = 1 THEN Sound(0, 0)    ' Turns off the sound (if any)
 #ENDIF
 FlushKeyb()
END SUB


SUB RefreshScreen()
 CLS
 DrawTable()
 ShowScore()
 LINE (vBorder, CurPos - PalWidth \ 2) - (vBorder + PalTick, CurPos + PalWidth / 2), 15, BF     ' Player 1
 IF GameMode <> 3 THEN LINE (xRes - vBorder - PalTick, CurPos2 - PalWidth \ 2) - (xRes - vBorder, CurPos2 + PalWidth / 2), 15, BF ' Player 2
 LINE (Ball.X - PalTick \ 2, Ball.Y - PalTick \ 2) - (Ball.X + PalTick \ 2, Ball.Y + PalTick \ 2), 15, BF  ' Ball
 SCREENCOPY 1, 0
 LastRefresh = uTIMER
END SUB


SUB FlushKeyb()
 WHILE LEN(INKEY) > 0: WEND
END SUB


SUB DrawTable()
 DIM AS INTEGER i
 FOR i = hBorder TO yRes - hBorder STEP 2 * PalTick
   LINE (xRes \ 2 - PalTick \ 2, i) - (xRes \ 2 + PalTick \ 2, i + PalTick), 7, BF  ' Central line
 NEXT i
 LINE (vBorder, hBorder - PalTick) - (xRes - vBorder, hBorder), 7, BF                 ' Up line
 LINE (vBorder, yRes - hBorder + PalTick) - (xRes - vBorder, yRes - hBorder), 7, BF   ' Down line
 IF GameMode = 3 THEN LINE (xRes - vBorder - PalTick, hBorder) - (xRes - vBorder, yRes - hBorder), 7, BF   ' Back line (Solo mode)
END SUB


SUB UpdateBallPosition()
 DIM AS DOUBLE a, b, c

 IF ABS(uTIMER - LastUpdate) > Mega THEN LastUpdate = uTIMER ' The machine could be paused, or anything...
 c = xRes * (Ball.Speed + 5) * ABS(uTIMER - LastUpdate) * 0.000000055
 LastUpdate = uTIMER

 SELECT CASE Ball.Angle
   CASE IS < 90
     b = COS((Ball.Angle MOD 90) * Degr2Rad) * c
     a = SQR(c^2 - b^2)
     Ball.X += a
     Ball.Y -= b
   CASE IS < 180
     a = COS((Ball.Angle MOD 90) * Degr2Rad) * c
     b = SQR(c^2 - a^2)
     Ball.X += a
     Ball.Y += b
   CASE IS < 270
     b = COS((Ball.Angle MOD 90) * Degr2Rad) * c
     a = SQR(c^2 - b^2)
     Ball.X -= a
     Ball.Y += b
   CASE ELSE
     a = COS((Ball.Angle MOD 90) * Degr2Rad) * c
     b = SQR(c^2 - a^2)
     Ball.X -= a
     Ball.Y -= b
 END SELECT
REM Solo mode handling...
 IF (Ball.X > xRes - vBorder - PalTick - PalTick \ 2) AND (Ball.Angle < 180) AND GameMode = 3 THEN
   Ball.Angle = 360 - Ball.Angle
   PlaySound(1)
 END IF
REM Player 1 handling...
 IF (Ball.X < vBorder + PalTick + PalTick \ 2) AND (Ball.Angle > 180) AND ABS(CurPos - Ball.Y) - (PalTick \ 2) <= PalWidth \ 2 THEN
   Ball.Angle = 360 - Ball.Angle
   Ball.Angle -= (CurPos - Ball.Y) / (PalWidth / 2) * 40
   IF Ball.Angle > 170 THEN Ball.Angle = 170
   IF Ball.Angle < 10 THEN Ball.Angle = 10
   PlaySound(2)
 END IF
REM Player 2 handling...
 IF (Ball.X > xRes - vBorder - PalTick - PalTick \ 2) AND (Ball.Angle < 180) AND ABS(CurPos2 - Ball.Y) - (PalTick \ 2) <= PalWidth \ 2 THEN
   Ball.Angle = 360 - Ball.Angle
   Ball.Angle += (CurPos2 - Ball.Y) / (PalWidth / 2) * 40
   IF Ball.Angle > 350 THEN Ball.Angle = 350
   IF Ball.Angle < 190 THEN Ball.Angle = 190
   PlaySound(2)
 END IF
REM Up & down bouncing
 IF Ball.Y > (yRes - hBorder - PalTick \ 2) AND (Ball.Angle > 90) AND (Ball.Angle < 180) THEN Ball.Angle = 180 - Ball.Angle: PlaySound(1)
 IF Ball.Y > (yRes - hBorder - PalTick \ 2) AND (Ball.Angle > 180) AND (Ball.Angle < 270) THEN Ball.Angle = 360 - (Ball.Angle MOD 180): PlaySound(1)
 IF Ball.Y < (hBorder + PalTick \ 2) AND (Ball.Angle < 90) THEN Ball.Angle = 180 - (Ball.Angle MOD 180): PlaySound(1)
 IF Ball.Y < (hBorder + PalTick \ 2) AND (Ball.Angle > 270) THEN Ball.Angle = 360 - (Ball.Angle MOD 180): PlaySound(1)
REM Checking if ball missed...
 IF (Ball.X < vBorder + PalTick + PalTick \ 2) AND (Ball.Angle > 180) AND ABS(CurPos - Ball.Y) - (PalTick \ 2) > PalWidth \ 2 THEN BallMissed = 1
 IF (Ball.X > xRes - vBorder - PalTick - PalTick \ 2) AND (Ball.Angle < 180) AND ABS(CurPos2 - Ball.Y) - (PalTick \ 2) > PalWidth \ 2 THEN BallMissed = 1
END SUB


SUB ShowScore()
 DIM AS BYTE Score1a, Score1b, Score2a, Score2b, x, y
 DIM AS INTEGER StartX, StartY
 IF Score1 > 99 THEN Score1 = 99
 IF Score2 > 99 THEN Score2 = 99
 Score1a = Score1 \ 10
 Score1b = Score1 MOD 10
 Score2a = Score2 \ 10
 Score2b = Score2 MOD 10

 StartY = hBorder + PalTick

 IF GameMode <> 3 THEN
   IF Score1a > 0 THEN
     StartX = xRes \ 2 - PalTick * 9
     FOR y = 1 TO 5
       FOR x = 1 TO 3
         IF BigNumbers(Score1a, y, x) = 1 THEN
           LINE (StartX + (x - 1) * PalTick, StartY + (y - 1) * PalTick) - (StartX + x * PalTick, StartY + y * PalTick), 7, BF
         END IF
       NEXT x
     NEXT y
   END IF

   StartX = xRes \ 2 - PalTick * 5
   FOR y = 1 TO 5
     FOR x = 1 TO 3
       IF BigNumbers(Score1b, y, x) = 1 THEN
         LINE (StartX + (x - 1) * PalTick, StartY + (y - 1) * PalTick) - (StartX + x * PalTick, StartY + y * PalTick), 7, BF
       END IF
     NEXT x
   NEXT y
 END IF

 IF Score2a > 0 OR GameMode = 4 THEN
   StartX = xRes \ 2 + PalTick * 2
   FOR y = 1 TO 5
     FOR x = 1 TO 3
       IF BigNumbers(Score2a, y, x) = 1 THEN
         LINE (StartX + (x - 1) * PalTick, StartY + (y - 1) * PalTick) - (StartX + x * PalTick, StartY + y * PalTick), 7, BF
       END IF
     NEXT x
   NEXT y
 END IF

 IF Score2a > 0 OR GameMode = 4 THEN StartX = xRes \ 2 + PalTick * 6 ELSE StartX = xRes \ 2 + PalTick * 2
 FOR y = 1 TO 5
   FOR x = 1 TO 3
     IF BigNumbers(Score2b, y, x) = 1 THEN
       LINE (StartX + (x - 1) * PalTick, StartY + (y - 1) * PalTick) - (StartX + x * PalTick, StartY + y * PalTick), 7, BF
     END IF
   NEXT x
 NEXT y
END SUB


SUB InitBall()
 Ball.Angle = 75 + INT(GenRnd() * 30)
 IF GameMode <> 4 THEN Ball.Speed = InitSpeed ELSE Ball.Speed = 2
 Ball.Y = (yRes / 2) - (yRes / 4) + GenRnd() * (yRes / 2)
 IF Ball.X < xRes \ 2 THEN Ball.X = vBorder + PalTick + PalTick + 1 ELSE Ball.X = xRes - vBorder - PalTick - PalTick + 1: Ball.Angle = 360 - Ball.Angle
END SUB


SUB QuitGame()
 #IFDEF __FB_DOS__
     IF Snd = 1 THEN Sound(0, 0)      ' Turns off the sound (if any)
   #ELSE
'      SDLNet_FreePacket(DataPacketRX)
'      SDLNet_FreePacket(DataPacketTX)
'      SDLNet_UDP_Close(UdpSrvSocket)
'      SDLNet_Quit ' Unload the SDL module
     IF Snd = 1 THEN
       SLEEP 100, 1                   ' Wait 1/10 of a second
       Mix_FreeChunk(SndFilePong)     ' Clearing allocated memory for sound files
       Mix_FreeChunk(SndFileBounce)   ' Clearing allocated memory for sound files
       Mix_FreeChunk(SndFileMissed)   ' Clearing allocated memory for sound files
       Mix_CloseAudio()               ' Close the SDL audio mixer
       SDL_Quit()                     ' Unload the SDL library
     END IF
 #ENDIF
 CLS
 OPEN EXEPATH + Delimiter + "empong.cfg" FOR OUTPUT AS #1
 PRINT #1, "# This is the configuration file of Emeritus Pong v"+ pVer + "."
 PRINT #1, "xRes=" & xRes
 PRINT #1, "yRes=" & yRes
 PRINT #1, "Snd=" & Snd
 PRINT #1, "ColorMode=" & ColorMode
 PRINT #1, "FullScreen=" & FullScreen
 PRINT #1, "InitSpeed=" & InitSpeed
 CLOSE #1
 SLEEP 100, 1
 END
END SUB


SUB MainMenu()
 DIM AS BYTE x, Choice = 1
 DIM AS STRING TitleBar
 DO
   Player1Cmd = 0              ' Initializing all values...
   Player2Cmd = 0
   CurPos = INT(yRes / 2)
   CurPos2 = INT(yRes / 2)
   PalWidth = CINT(yRes * 14 / 100)
   PalTick = CINT(xRes * 2 / 100)
   hBorder = CINT(yRes * 6 / 100)
   vBorder = CINT(xRes * 3 / 100)
   BallMissed = 0
   SyncCounter = 0
   ' *** GameMode:   1: vs CPU   2: vs Player  3: Solo   4: Pong Clock
   InitBall()
   Score1 = 0
   Score2 = 0

   #IFDEF __FB_DOS__
     SCREEN 0     ' In DOS, just initialize 80x25.
     WIDTH 80, 30 ' and turn off the cursor.
     LOCATE ,,0
    #ELSE         ' Otherwise use video subsystem.
     SCREEN 12,,,FullScreen
     SETMOUSE ,,0  ' Disables the mouse cursor
   #ENDIF

   COLOR 7, 0
   CLS
   FOR x = 1 TO HIWORD(WIDTH())
     LOCATE x, 1: PRINT STRING(80, CHR(176));
   NEXT x
   COLOR 0, 10
   TitleBar = "Emeritus Pong v" + pVer + " Copyright (C) Mateusz Viste " + pDate
   LOCATE 1, 1: PRINT SPACE(80);
   LOCATE 1, 40 - LEN(TitleBar) \ 2: PRINT TitleBar;
   COLOR 7, 0
   LOCATE 9, 31:  PRINT CHR(201); STRING(16, 205); CHR(187);  ' Draws menu box
   LOCATE 10, 31: PRINT CHR(186); STRING(16, 32); CHR(186);
   LOCATE 11, 31: PRINT CHR(186); STRING(16, 32); CHR(186);
   LOCATE 12, 31: PRINT CHR(186); STRING(16, 32); CHR(186);
   LOCATE 13, 31: PRINT CHR(186); STRING(16, 32); CHR(186);
   LOCATE 14, 31: PRINT CHR(186); STRING(16, 32); CHR(186);
   LOCATE 15, 31: PRINT CHR(186); STRING(16, 32); CHR(186);
   LOCATE 16, 31: PRINT CHR(200); STRING(16, 205); CHR(188);

   LOCATE 20, 24: PRINT CHR(218); STRING(30, 196); CHR(191);  ' Draws info box
   LOCATE 21, 24: PRINT CHR(179); STRING(30, 32); CHR(179);
   LOCATE 22, 24: PRINT CHR(179); STRING(30, 32); CHR(179);
   LOCATE 23, 24: PRINT CHR(179); STRING(30, 32); CHR(179);
   LOCATE 24, 24: PRINT CHR(192); STRING(30, 196); CHR(217);

   DO
     IF Choice = 1 THEN COLOR 15, 1 ELSE COLOR 7, 0
     LOCATE 10, 32: PRINT "  Play vs CPU   ";
     IF Choice = 2 THEN COLOR 15, 1 ELSE COLOR 7, 0
     LOCATE 11, 32: PRINT "  2 Players game";
     IF Choice = 3 THEN COLOR 15, 1 ELSE COLOR 7, 0
     LOCATE 12, 32: PRINT "  Play solo     ";
     IF Choice = 4 THEN COLOR 15, 1 ELSE COLOR 7, 0
     LOCATE 13, 32: PRINT "  Settings      ";
     IF Choice = 5 THEN COLOR 15, 1 ELSE COLOR 7, 0
     LOCATE 14, 32: PRINT "  Pong Clock    ";
     IF Choice = 6 THEN COLOR 15, 1 ELSE COLOR 7, 0
     LOCATE 15, 32: PRINT "  Quit game     ";
     COLOR 7, 0
     SELECT CASE Choice
       CASE 1
         LOCATE 21, 26: PRINT "The player plays against the";
         LOCATE 22, 26: PRINT "computer. Use up/down arrows"
         LOCATE 23, 26: PRINT "to move and 'P' to pause.   ";
       CASE 2
         LOCATE 21, 26: PRINT "Two-players mode. The second";
         LOCATE 22, 26: PRINT "player uses +/- keys on the ";
         LOCATE 23, 26: PRINT "numeric pad.                ";
       CASE 3
         LOCATE 21, 26: PRINT "The 'solo' mode hasn't any  ";
         LOCATE 22, 26: PRINT "opponent. The player plays  ";
         LOCATE 23, 26: PRINT "alone against a wall.       ";
       CASE 4
         LOCATE 21, 26: PRINT "Game settings (screen size, ";
         LOCATE 22, 26: PRINT "sound...)                   ";
         LOCATE 23, 26: PRINT "                            ";
       CASE 5
         LOCATE 21, 26: PRINT "Demo mode. The current time ";
         LOCATE 22, 26: PRINT "is displayed in place of the";
         LOCATE 23, 26: PRINT "players' scores.            ";
       CASE 6
         LOCATE 21, 26: PRINT "Quits the game and returns  ";
         LOCATE 22, 26: PRINT "to the OS. Are you sure you ";
         LOCATE 23, 26: PRINT "want that??                 ";
     END SELECT
     SLEEP
     LastKey = INKEY
     IF LastKey = CHR(27) OR LastKey = CHR(255) + "k" THEN QuitGame()
     IF LastKey = CHR(255) + "H" AND Choice > 1 THEN Choice -= 1
     IF LastKey = CHR(255) + "P" AND Choice < 6 THEN Choice += 1
     FlushKeyb()
   LOOP UNTIL LastKey = CHR(13)
   SELECT CASE Choice
     CASE 1
       GameMode = 1
       PlayGame()
     CASE 2
       GameMode = 2
       'NetGame.state = 1  ' Temporary hack for testing purpose - FIXME!
       'CLS
       'LINE INPUT "server or client? ", NetGame.LocalMode
       'IF NetGame.LocalMode = "client" THEN
       '  LINE INPUT "What remote IP? ", NetGame.RemoteIP
       'END IF
       'InitNetworkSocket()
       PlayGame()
     CASE 3
       GameMode = 3
       PlayGame()
     CASE 4
       Setup()
     CASE 5
       GameMode = 4
       PlayGame()
     CASE 6
       QuitGame()
   END SELECT
 LOOP
END SUB


SUB PlaySound(SndType AS BYTE)
 REM   SndTypes:  1: Bounce from up/down walls [Bounce]
 REM              2: Bounce from palette [Pong]
 REM              3: Ball Missed [Missed]
 IF Snd = 1 THEN
   #IFDEF __FB_DOS__
     SELECT CASE SndType
       CASE 1
         SOUND(1600, 10)
       CASE 2
         SOUND(1000, 10)
       CASE 3
         DIM i AS INTEGER
         FOR i = 500 TO 300 STEP -25
           SOUND(i, 5)
           SLEEP 1, 1
         NEXT i
     END SELECT
    #ELSE
     SELECT CASE SndType
       CASE 1
         Mix_PlayChannel(-1, SndFileBounce, 0)
       CASE 2
         Mix_PlayChannel(-1, SndFilePong, 0)
       CASE 3
         Mix_PlayChannel(-1, SndFileMissed, 0)
     END SELECT
   #ENDIF
 END IF
END SUB


SUB Setup()
 DIM AS BYTE x, Choice, Resolution
 DIM ResList(1 TO 5) AS STRING => {"320x240  ", "640x480  ", "800x600  ", "1024x768 ", "1280x1024"}
 DIM SoundList(0 TO 1) AS STRING => {"Off      ", "On       "}
 DIM VideoList(0 TO 1) AS STRING => {"Mono     ", "Color    "}
 DIM NoYes(0 TO 1) AS STRING => {"No       ", "Yes      "}
 Choice = 1
 SELECT CASE xRes
   CASE 320
     Resolution = 1
   CASE 640
     Resolution = 2
   CASE 800
     Resolution = 3
   CASE 1024
     Resolution = 4
   CASE 1280
     Resolution = 5
 END SELECT
 FOR x = 2 TO HIWORD(WIDTH())
   LOCATE x, 1: PRINT STRING(80, CHR(176));
 NEXT x
 COLOR 7, 0
 LOCATE 9, 28:  PRINT CHR(201); STRING(22, 205); CHR(187);  ' Draws menu box
 LOCATE 10, 28: PRINT CHR(186); STRING(22, 32); CHR(186);
 LOCATE 11, 28: PRINT CHR(186); STRING(22, 32); CHR(186);
 LOCATE 12, 28: PRINT CHR(186); STRING(22, 32); CHR(186);
 LOCATE 13, 28: PRINT CHR(186); STRING(22, 32); CHR(186);
 LOCATE 14, 28: PRINT CHR(186); STRING(22, 32); CHR(186);
 LOCATE 15, 28: PRINT CHR(186); STRING(22, 32); CHR(186);
 LOCATE 16, 28: PRINT CHR(200); STRING(22, 205); CHR(188);

 LOCATE 20, 24: PRINT CHR(218); STRING(30, 196); CHR(191);  ' Draws info box
 LOCATE 21, 24: PRINT CHR(179); STRING(30, 32); CHR(179);
 LOCATE 22, 24: PRINT CHR(179); STRING(30, 32); CHR(179);
 LOCATE 23, 24: PRINT CHR(179); STRING(30, 32); CHR(179);
 LOCATE 24, 24: PRINT CHR(192); STRING(30, 196); CHR(217);

 DO
   IF Choice = 1 THEN COLOR 15, 1 ELSE COLOR 7, 0
   LOCATE 10, 29: PRINT " Resolution: "; ResList(Resolution);
   IF Choice = 2 THEN COLOR 15, 1 ELSE COLOR 7, 0
   LOCATE 11, 29: PRINT " Fullscreen: "; NoYes(FullScreen)
   IF Choice = 3 THEN COLOR 15, 1 ELSE COLOR 7, 0
   LOCATE 12, 29: PRINT " Sound:      "; SoundList(Snd);
   IF Choice = 4 THEN COLOR 15, 1 ELSE COLOR 7, 0
   LOCATE 13, 29: PRINT " Video Mode: "; VideoList(ColorMode);
   IF Choice = 5 THEN COLOR 15, 1 ELSE COLOR 7, 0
   LOCATE 14, 29: PRINT " Ball speed:"; InitSpeed; SPACE(8 - InitSpeed \ 10)
   IF Choice = 6 THEN COLOR 15, 1 ELSE COLOR 7, 0
   LOCATE 15, 29: PRINT " Go back to main menu ";
   COLOR 7, 0
   SELECT CASE Choice
     CASE 1
       LOCATE 21, 26: PRINT "Game's resolution. On Linux ";
       LOCATE 22, 26: PRINT "and Windows it will be the  "
       LOCATE 23, 26: PRINT "window's dimensions.        ";
     CASE 2
       LOCATE 21, 26: PRINT "Switches the full-screen    ";
       LOCATE 22, 26: PRINT "mode ON/OFF. This option    ";
       LOCATE 23, 26: PRINT "is ignored in DOS.          ";
     CASE 3
       LOCATE 21, 26: PRINT "Enables / Disables sound.   ";
       LOCATE 22, 26: PRINT "DOS: Internal PC speaker    ";
       LOCATE 23, 26: PRINT "Linux/Windows: Sound card   ";
     CASE 4
       LOCATE 21, 26: PRINT "There you may choose if you ";
       LOCATE 22, 26: PRINT "wants a monochrome video    ";
       LOCATE 23, 26: PRINT "or a color one.             ";
     CASE 5
       LOCATE 21, 26: PRINT "Sets the ball's speed. Note,";
       LOCATE 22, 26: PRINT "that the speed is increasing";
       LOCATE 23, 26: PRINT "during the game.            ";
     CASE 6
       LOCATE 21, 26: PRINT "Go back to the main menu.   ";
       LOCATE 22, 26: PRINT "                            ";
       LOCATE 23, 26: PRINT "                            ";
   END SELECT
   SLEEP
   LastKey = INKEY
   IF LastKey = CHR(255) + "H" AND Choice > 1 THEN Choice -= 1
   IF LastKey = CHR(255) + "P" AND Choice < 6 THEN Choice += 1
   IF LastKey = CHR(255) + "k" THEN QuitGame()
   IF LastKey = CHR(13) THEN
     SELECT CASE Choice
       CASE 1
         Resolution += 1
         IF Resolution > 5 THEN Resolution = 1
         SELECT CASE Resolution
           CASE 1
             xRes = 320
             yRes = 240
           CASE 2
             xRes = 640
             yRes = 480
           CASE 3
             xRes = 800
             yRes = 600
           CASE 4
             xRes = 1024
             yRes = 768
           CASE 5
             xRes = 1280
             yRes = 1024
         END SELECT
       CASE 2
         FullScreen = 1 - FullScreen
       CASE 3
         Snd = 1 - Snd
       CASE 4
         ColorMode = 1 - ColorMode
       CASE 5
         InitSpeed += 1
         IF InitSpeed = 13 THEN InitSpeed = 1
     END SELECT
   END IF
   FlushKeyb()
 LOOP UNTIL (LastKey = CHR(13) AND Choice = 6) OR LastKey = CHR(27)
END SUB


SUB LoadConfig()
 DIM DefaultConfig AS BYTE
 DefaultConfig = 0
 xRes = VAL(ReadCfg(EXEPATH + Delimiter + "empong.cfg", "xRes"))
 yRes = VAL(ReadCfg(EXEPATH + Delimiter + "empong.cfg", "yRes"))
 Snd = VAL(ReadCfg(EXEPATH + Delimiter + "empong.cfg", "Snd"))
 ColorMode = VAL(ReadCfg(EXEPATH + Delimiter + "empong.cfg", "ColorMode"))
 FullScreen = VAL(ReadCfg(EXEPATH + Delimiter + "empong.cfg", "FullScreen"))
 InitSpeed = VAL(ReadCfg(EXEPATH + Delimiter + "empong.cfg", "InitSpeed"))

 IF xRes < 20 THEN DefaultConfig = 1
 IF yRes < 20 THEN DefaultConfig = 1
 IF Snd < 0 OR Snd > 1 THEN DefaultConfig = 1
 IF ColorMode < 0 OR ColorMode > 1 THEN DefaultConfig = 1
 IF FullScreen < 0 OR FullScreen > 1 THEN DefaultConfig = 1
 IF InitSpeed < 1 OR InitSpeed > 12 THEN DefaultConfig = 1
 IF DefaultConfig = 1 THEN
   xRes = 640
   yRes = 480
   Snd = 1
   ColorMode = 1
   FullScreen = 0
   InitSpeed = 2
 END IF
END SUB


FUNCTION ReadCFG(CFGfile AS STRING, CFGField AS STRING) AS STRING
STATIC CfgTable(1 TO 2, 0 TO 255) AS STRING
DIM AS STRING CfgReturnString, CfgTmpBuffer
DIM AS INTEGER Counter

IF CfgTable(1, 0) <> "init ok" THEN
  DIM AS INTEGER CfgFileHandler, CfgColonPos, Counter
  CfgFileHandler = FREEFILE
  CfgReturnString = ""
  Counter = 0
  CfgTable(1, 0) = "init ok"
  IF DIR(CFGfile) <> "" THEN
     OPEN CFGfile FOR INPUT AS #CfgFileHandler
     DO
        Counter += 1
        LINE INPUT #CfgFileHandler, CfgTmpBuffer
        IF MID(TRIM(CfgTmpBuffer), 1, 1) <> "#" THEN
          CfgColonPos = INSTR(CfgTmpBuffer, "=")
          CfgTable(1, Counter) = TRIM(MID(CfgTmpBuffer, 1, CfgColonPos - 1))
          CfgTable(2, Counter) = TRIM(MID(CfgTmpBuffer, CfgColonPos + 1))
        END IF
     LOOP UNTIL EOF(CfgFileHandler) OR Counter = 255
     CLOSE #CfgFileHandler
  END IF
  CfgTable(2, 0) = STR(Counter)
END IF

Counter = 0
DO
  Counter += 1
  IF UCASE(CfgTable(1, Counter)) = UCASE(CFGField) THEN CfgReturnString = CfgTable(2, Counter)
LOOP UNTIL CfgReturnString <> "" OR Counter >= VAL(CfgTable(2, 0))

RETURN CfgReturnString
END FUNCTION


FUNCTION GenRnd(BYVAL NewGen AS BYTE = 0) AS DOUBLE
 STATIC i AS INTEGER
 STATIC RandomTable(1 TO 1024) AS DOUBLE
 DIM AS DOUBLE Wynik
 IF NewGen = 0 THEN
    i += 1
    IF i > 1025 THEN i = 1
    Wynik = RandomTable(i)
    RETURN Wynik
   ELSE
     RANDOMIZE TIMER, 3
     FOR i = 1 TO 1024
       RandomTable(i) = RND
     NEXT i
     i = 0
 END IF
END FUNCTION


SUB Pause()
 DIM AS INTEGER i
 #IFDEF __FB_DOS__
   IF Snd = 1 THEN Sound(0, 0)    ' Turns off the sound (if any)
 #ENDIF
 FOR i = i TO yRes STEP 2
   LINE (0, i)-(xRes, i), 0
 NEXT i
 PCOPY 1, 0
 SLEEP 300, 1
 FOR i = 0 TO xRes STEP 2
   LINE (i, 0)-(i, yRes), 0
 NEXT i
 PCOPY 1, 0
 SLEEP
 SLEEP 100, 1
 Player1LastMove = uTIMER   ' Resync all game's variables
 Player2LastMove = uTIMER   '
 LastUpdate = uTIMER        '
 FlushKeyb
END SUB


SUB Sound(BYVAL freq AS INTEGER, dur AS UINTEGER)
 STATIC SoundDuration AS UINTEGER
 STATIC SoundStarted AS DOUBLE
 IF freq <= 0 THEN
     IF (ABS(TIMER - SoundStarted) * 100 > SoundDuration) OR (freq = 0) THEN
       OUT &H61, INP(&H61) AND &HFC
     END IF
   ELSE
     DIM t AS DOUBLE, f1 AS USHORT
     IF freq > 0 THEN
       f1 = 1193181 \ freq
       OUT &H61, INP(&H61) OR 3
       OUT &H43, &HB6
       OUT &H42, lobyte(f1)
       OUT &H42, hibyte(f1)
     END IF
     SoundStarted = TIMER
     SoundDuration = dur
   END IF
END SUB


SUB CheckPlayersInputs()
 Player1Cmd *= 2
 Player2Cmd *= 2
 IF GameMode <> 4 THEN
   IF MultiKey(&h48) = -1 THEN Player1Cmd = 1       ' Up
   IF MultiKey(&h50) = -1 THEN Player1Cmd = -1      ' Down
 END IF
 IF GameMode = 2 THEN
   IF MultiKey(&h4A) = -1 THEN Player2Cmd = 1        ' Up (player 2)
   IF MultiKey(&h4E) = -1 THEN Player2Cmd = -1       ' Down (player 2)
 END IF
 IF (GameMode = 1 OR GameMode = 4) THEN
   IF Ball.Angle < 180 THEN
       IF Ball.Angle > 90 THEN
           IF CurPos2 - Ball.Y < (yres / 15) AND (Player2Cmd < 0 OR ABS(uTIMER - AiLastDecision) > AiSpeed * Mega) THEN Player2Cmd = -1 : AiLastDecision = uTIMER
           IF CurPos2 - Ball.Y > (yres / 15) AND (Player2Cmd > 0 OR ABS(uTIMER - AiLastDecision) > AiSpeed * Mega) THEN Player2Cmd = 1 : AiLastDecision = uTIMER
         ELSE
           IF Ball.Y - CurPos2 > (yres / 15) AND (Player2Cmd < 0 OR ABS(uTIMER - AiLastDecision) > AiSpeed * Mega) THEN Player2Cmd = -1 : AiLastDecision = uTIMER
           IF Ball.Y - CurPos2 < (yres / 15) AND (Player2Cmd > 0 OR ABS(uTIMER - AiLastDecision) > AiSpeed * Mega) THEN Player2Cmd = 1 : AiLastDecision = uTIMER
       END IF
   END IF
 END IF
 IF GameMode = 4 THEN
   IF Ball.Angle > 180 THEN
       IF Ball.Angle < 270 THEN
           IF CurPos - Ball.Y < (yres / 15) AND (Player1Cmd < 0 OR ABS(uTIMER - AiLastDecision) > AiSpeed * Mega) THEN Player1Cmd = -1 : AiLastDecision = uTIMER
           IF CurPos - Ball.Y > (yres / 15) AND (Player1Cmd > 0 OR ABS(uTIMER - AiLastDecision) > AiSpeed * Mega) THEN Player1Cmd = 1 : AiLastDecision = uTIMER
         ELSE
           IF Ball.Y - CurPos > (yres / 15) AND (Player1Cmd < 0 OR ABS(uTIMER - AiLastDecision) > AiSpeed * Mega) THEN Player1Cmd = -1 : AiLastDecision = uTIMER
           IF Ball.Y - CurPos < (yres / 15) AND (Player1Cmd > 0 OR ABS(uTIMER - AiLastDecision) > AiSpeed * Mega) THEN Player1Cmd = 1 : AiLastDecision = uTIMER
       END IF
   END IF
 END IF
 IF ABS(Player1Cmd) > 1 THEN Player1Cmd = 0
 IF ABS(Player2Cmd) > 1 THEN Player2Cmd = 0
REM Check general stuff (Pause, Escape)...
 IF MultiKey(&h19) = -1 AND GameMode <> 4 THEN Pause()  ' "P"
 IF MultiKey(&h01) = -1 THEN EscPressed = 1             ' ESC
END SUB


SUB UpdatePlayersPositions()
  REM Player 1...
   IF Player1Cmd = 1 THEN CurPos -= ABS(uTIMER - Player1LastMove) / Mega * yRes / 1.5  ' Up
   IF Player1Cmd = -1 THEN CurPos += ABS(uTIMER - Player1LastMove) / Mega * yRes / 1.5  ' Down
   IF CurPos - PalWidth / 2 - hBorder < 1 THEN CurPos = hBorder + PalWidth / 2 + 1
   IF CurPos + PalWidth / 2 + hBorder > yRes - 1 THEN CurPos = yRes - hBorder - PalWidth / 2 - 1
   Player1LastMove = uTIMER
  REM Player 2...
   IF Player2Cmd = 1 THEN CurPos2 -= ABS(uTIMER - Player2LastMove) / Mega * yRes / 1.5  ' Up
   IF Player2Cmd = -1 THEN CurPos2 += ABS(uTIMER - Player2LastMove) / Mega * yRes / 1.5  ' Down
   IF CurPos2 - PalWidth / 2 - hBorder < 1 THEN CurPos2 = hBorder + PalWidth / 2 + 1
   IF CurPos2 + PalWidth / 2 + hBorder > yRes - 1 THEN CurPos2 = yRes - hBorder - PalWidth / 2 - 1
   Player2LastMove = uTIMER
END SUB


FUNCTION RDTSC() AS ULONGINT  ' Requires Pentium or better
  ASM
    rdtsc
    mov [Function], eax
    mov [Function+4], edx
  END ASM
END FUNCTION


FUNCTION uTIMER() AS ULONGINT
 DIM AS ULONGINT Wynik
 #IFDEF __FB_DOS__
   STATIC CpuClock AS ULONGINT
   IF CpuClock > 0 THEN
       Wynik = RDTSC() \ CpuClock
     ELSE
       DIM AS DOUBLE TempDouble
       SLEEP 100, 1
       TempDouble = TIMER
       CpuClock = RDTSC()
       DO: LOOP UNTIL ABS(TIMER - TempDouble) >= 1
       CpuClock = ABS(RDTSC() - CpuClock) \ 1000000
   END IF
  #ELSE
   Wynik = TIMER * 1000000
 #ENDIF
 RETURN Wynik
END FUNCTION


'SUB NetGameSendSync()
'  DIM AS STRING DataBuffer
'  SELECT CASE Netgame.LocalMode
'    CASE "server"
'      DataBuffer = "SRV" & AiSpeed & ";" & CurPos & ";" & Score1 & ";" & Score2 & ";" & Ball.Speed & ";" & Ball.Angle & ";" & Ball.X & ";" & Ball.Y & ";"
'    CASE "client"
'      DataBuffer = "CLT" & CurPos2
'  END SELECT
'  REM  Here I have to send the DataBuffer
'END SUB


'SUB NetGameGetSync()
'  DIM AS STRING DataBuffer
'  DIM AS INTEGER PacketRxFlag
'  REM Here let's check if we got a packet.
'  PacketRxFlag = SDLNet_UDP_Recv(UdpSrvSocket, DataPacketRX)
'  SELECT CASE Netgame.LocalMode
'    CASE "server"
     'CurPos2 = ""
'    CASE "client"
     ' AiSpeed = ""
     ' CurPos = ""
     ' Score1 = ""
     ' Score2 = ""
     ' BallParams.Speed = ""
     ' BallParams.Angle = ""
     ' BallParams.X = ""
     ' BallParams.Y = ""
'  END SELECT
'END SUB


'SUB InitNetworkSocket()
'  DIM AS INTEGER PacketRXflag
'  DIM DataMsg AS ZSTRING*42 => "THIS IS AN EMPONG CLIENT. ANYBODY'S THERE?"
'  IF SDLNet_Init <> 0 THEN
'    PRINT "Error: Failed to initialize the SDLnet library."
'    END(1)
'  END IF
'  ' Create emtpy structures for UDP RX/TX packets with a capacity of 256 bytes.
'  DataPacketRX = SDLNet_AllocPacket(256)
'  DataPacketTX = SDLNet_AllocPacket(256)
'  SELECT CASE NetGame.LocalMode
'    CASE "server"
'      NetGame.LocalPort = 7495
'      UdpSrvSocket = SDLNet_UDP_Open(7495)
'      IF UdpSrvSocket = 0 THEN
'        PRINT "Error: Failed to bind to the local UDP/7495 socket."
'        END(1)
'      END IF
'      PRINT "Waiting for a client to connect..."
'      DO
'        PacketRxFlag = SDLNet_UDP_Recv(UdpSrvSocket, DataPacketRX)
'        SLEEP 200, 1 ' Wait 0.2s before repolling
'      LOOP UNTIL PacketRxFlag = 1
'      NetGame.RemotePort = DataPacketRX->address.port
'      PRINT "Got a connection from " & IPaddressToString(DataPacketRX->address.host) & ":" & NetGame.RemotePort & "."
'      SLEEP 2000, 1
'    CASE "client"
'      UdpSrvSocket = SDLNet_UDP_Open(0)
'      IF UdpSrvSocket = 0 THEN
'        PRINT "Error: Failed to bind to a local UDP socket."
'        END(1)
'      END IF
'      NetGame.LocalPort = UdpSrvSocket
'      NetGame.RemotePort = 7495
'      DataPacketTX->channel = -1
'      DataPacketTX->len = 42
'      DataPacketTX->data = @DataMsg
'      'SDLNet_ResolveHost(DataPacketTX->address, NetGame.RemoteIP, 7495)
'      DataPacketTX->address.host = VAL("&b00000001000000000000000001111111") ' 127.0.0.1
'      DataPacketTX->address.port = 7495
'      PRINT "Waiting for the server to answer... "
'      SDLNet_UDP_Send(UdpSrvSocket, DataPacketTX->channel, DataPacketTX)
'  END SELECT
'END SUB


'FUNCTION IPaddressToString(sdlIP AS UINT32) AS STRING
'  RETURN "" & (sdlIP AND &b11111111) & "." & ((sdlIP AND &b1111111100000000) SHR 8) & "." & ((sdlIP AND &b111111110000000000000000) SHR 16) & "." & ((sdlIP AND &b11111111000000000000000000000000) SHR 24)
'END FUNCTION