This is a listing of the Connect4.exw program (Connect4 Game):

-- Connect4.exw ------------------------------------------------------------
-- A Euphoria version of the well known Connect-4 game:
-- Game Objective: The first player (you or computer) to get 4 of their tiles
-- in a straight line (diagonal,vertical or horizontal) wins!
-- -------------------------------------------------------------------------
-- (c) Fred Mangan 2010

-- Some stratagies:
-- On each player's play, the computer must check if there is a winner, and
-- if so, must report who won and end the game.

-- Scanning routine(s) will help with this and also will help in finding a
-- play move for the computer.

-- An outline of strategy for the computer's moves would be:
--  1. Is there a winning move for computer? If so make it.
--  2. Will human win on his/her next move? If so, block it, if can.
--  3. Is there a move that will advance the computer's most developed
--     line of tiles? If so, make it.
--  4. If MoveCounter > MAX_MOVES, game over, no winner.
--  5. Make a random move.
-- This strategy can be refined later.

--Skeleton's code generated by "The Window Designer for EuWinGUI Applications"
--(C)2001-2006 by Andrea Cini.
--***************************************************************************

include EuWinGUI.ew
include eucrash.ew    -- to handle credits
--***************************************************************************

--Handles of the controls
atom  ConnectGroup,PromptText,CreditsButton,CloseButton,LogoDialog,
      LogoPicture
--***************************************************************************

atom FontA
--***************************************************************************
-- My stuff:

    -- The grid on WinHwnd is represented by 12 rows of PictureButtons, each
    -- row containing 12 buttons. The handles of these buttons are stored
    -- in sequence Tiles. Game activity is simulated by laying suitable small
    -- BMP pictures on the PictureButtons.
    sequence Tiles    -- a sequence to hold all the PictureButtons

    -- Game activity is shadowed by data in sequence PlayTable, which is used
    -- as the basis for assessing the state of play.
    sequence PlayTable                    -- working table for players' moves

    constant WINSIZE = {450,387},       -- size for WinHwnd
             CONPOSN = {10,5},          -- position, TLH cormer of grid
             CONSIZE = {420,325},       -- size for the grid

             -- BMP for introductory picture
             LOGO_PIC = "Resources\\Logo.bmp",

             -- BMPs for tiles
             TILE_PICS = {"Resources\\MtPic.bmp",
                          "Resources\\PlayerPic.bmp",
                          "Resources\\ComputerPic.bmp"},

             -- sound files
                 BOING = "Resources\\Boing.wav",
                 CHIRP = "Resources\\Chirps.wav",

            PROMPT_TXT = " Click on a column to place your tile. ",

            INTRO_MSG  = "The first player (you or computer) to get 4 of \n" &
                         " their tiles in a straight line (diagonal,vertical\n" &
                         " or horizontal) wins!\n\n" &
                         "Will you play first?",

             NULL_ID = 1,    -- Entity IDs, also indices to TILE_PICS
             HUMAN_ID = 2,
             COMPUTER_ID = 3,

             NROWS = 12,
             NCOLS = 12,
             MAX_MOVES = NROWS * NCOLS,    -- i.e.,when array is full

             TILE_CLR_DATA = 1,            -- more indices
             TILE_POS_DATA = 2

    integer GameIsOn   GameIsOn = False -- flag
    integer MoveCounter                    -- counts moves in a game
    integer ActivePlayer                -- flags which player's turn is next

procedure TimeDelayLoop()
-- Delays after a SetWinTimer() command,
-- when only a Time Event is needed.
-- (most accurate if use WaitEvent here)

    while True do    -- endless loop

        WaitEvent()  -- wait for a windows' event

        if Event = Time then

            -- time is up; jump out of loop
            exit

        end if

    end while

end procedure -- TimeDelayLoop

procedure TerminateProgram()
-- Tidy up and close program

    EndCredits2()    -- removes a memory bitmap
    CloseApp(0)

end procedure -- TerminateProgram

procedure InitializeButtonPictures()
-- Places an "empty" picture on each of the PictureButtons

    for i = 1 to length(Tiles) do

        for j = 1 to length(Tiles[i]) do

            SetPicture(Tiles[i][j], PictureButton, TILE_PICS[NULL_ID])
            SetDim(Tiles[i][j],32,25)

        end for

    end for

end procedure -- InitializeButtonPictures

procedure InitializePlayTable()
-- PlayTable is a square table, reflecting the Tiles, and used to keep
-- track of what tiles players have played. It is initiallized to all-zeroes.

    PlayTable = repeat(repeat(0,NCOLS),NROWS)

end procedure -- InitializePlayTable

procedure CreatePictureButtons()
-- Creates the PictureButtons used to represent the tiles.
-- It would be too tedious to create these in Designer, also
-- doing it this way gives Tiles sequence a row, column structure.
-- The buttons' handles are saved in array Tiles in row-column form.
    sequence tile_size,tile_offsets,temp_row
    atom butn

    tile_size = CONSIZE / 13    -- adjusted by trial-and-error
    tile_offsets = CONPOSN + 12

    Tiles = {}                              -- initial to empty list
    for row = 1 to NROWS do

        temp_row = {}                        -- accumulates a row of buttons

        -- build a row of buttons
        for col = 1 to NCOLS do

            -- create a button's handle
            butn = Control(PictureButton,
                           "",
                           tile_offsets[1] + floor(tile_size[1]*(col-1)),
                           tile_offsets[2],
                           tile_size[1],tile_size[2])

            temp_row = append(temp_row,butn)    -- add button handle to sequence

        end for

        Tiles = append(Tiles,temp_row)            -- add the row to Tiles

        tile_offsets[2] += tile_size[2]            -- advance down to next row

    end for

end procedure -- CreatePictureButtons

procedure InitializeNewGame()
-- Sets up to play a new game

    MoveCounter = 0
    InitializeButtonPictures()
    InitializePlayTable()

    if AskMsg(INTRO_MSG,"First Player") then

        -- human kicks off
        ActivePlayer = HUMAN_ID
        SetText(PromptText,PROMPT_TXT)

    else

        -- computer kicks off
        ActivePlayer = COMPUTER_ID
        SetText(PromptText,"")

    end if

    -- flag game is in progress
    GameIsOn = True

end procedure -- InitializeNewGame

function GenScanPlayTable()
-- Procedure scans the PlayTable and returns a sequence, Scan, containing 4
-- sub-sequences. The sub-sequences are:
-- Scan[1][1]: values in diagonals sloping down to left in Playtable
-- Scan[1][2]: corresponding row-col coords for values in Scan[1][1]
-- Scan[2][1]: values in diagonals sloping down to right in PlayTable
-- Scan[2][2]: corresponding row-col coords for values in Scan[2][1]
-- Scan[3][1]: values in horizontals in PlayTable
-- Scan[3][2]: corresponding row-col coords for values in Scan[3][1]
-- Scan[4][1]: values in verticals in PlayTable
-- Scan[4][2]: corresponding row-col coords for values in Scan[4][1]
-- This routine has scope for optimization and generalization.
    sequence Scan, top_scan, side_scan, temp, temp2, lim
    integer n

    Scan = repeat({{},{}}, 4)    -- skeleton sequence for Scan

    -- 1. this is for diagonals downwards to left
    -- a. track across top
    for col = 1 to NCOLS do
        top_scan = {1,col}
        lim = {col,1}
        temp = {}
        temp2 = {}

        while top_scan[1] <= lim[1] and top_scan[2] >= lim[2] do
            n = PlayTable[top_scan[1]][top_scan[2]]
            temp = append(temp, n)
            temp2 = append(temp2, {top_scan[1],top_scan[2]})
            top_scan += {1,-1}
        end while

        Scan[1][TILE_CLR_DATA] = append(Scan[1][TILE_CLR_DATA], temp)
        Scan[1][TILE_POS_DATA] = append(Scan[1][TILE_POS_DATA], temp2)
    end for

    -- b. track down RHS
    for row = 2 to NROWS do
        side_scan = {row,NCOLS}
        lim = {NCOLS,row}
        temp = {}
        temp2 = {}

        while side_scan[1] <= lim[1] and side_scan[2] >= lim[2] do
            n = PlayTable[side_scan[1]][side_scan[2]]
            temp = append(temp, n)
            temp2 = append(temp2, {side_scan[1],side_scan[2]})
            side_scan += {1,-1}
        end while

        Scan[1][TILE_CLR_DATA] = append(Scan[1][TILE_CLR_DATA], temp)
        Scan[1][TILE_POS_DATA] = append(Scan[1][TILE_POS_DATA], temp2)
    end for

    -- 2. this is for diagonals downwards to right
    -- a. track across top
    for col = 1 to NCOLS do
        top_scan = {1,col}
        lim = {NROWS,NCOLS}
        temp = {}
        temp2 = {}

        while top_scan[1] <= lim[1] and top_scan[2] <= lim[2] do
            n = PlayTable[top_scan[1]][top_scan[2]]
            temp = append(temp, n)
            temp2 = append(temp2, {top_scan[1],top_scan[2]})
            top_scan += {1,1}
        end while

        Scan[2][TILE_CLR_DATA] = append(Scan[2][TILE_CLR_DATA], temp)
        Scan[2][TILE_POS_DATA] = append(Scan[2][TILE_POS_DATA], temp2)
    end for

    -- b. track down LHS
    for row = 2 to NROWS do
        side_scan = {row,1}
        lim = {NROWS,NCOLS}
        temp = {}
        temp2 = {}

        while side_scan[1] <= lim[1] and side_scan[2] <= lim[2] do
            n = PlayTable[side_scan[1]][side_scan[2]]
            temp = append(temp, n)
            temp2 = append(temp2, {side_scan[1],side_scan[2]})
            side_scan += {1,1}
        end while

        Scan[2][TILE_CLR_DATA] = append(Scan[2][TILE_CLR_DATA], temp)
        Scan[2][TILE_POS_DATA] = append(Scan[2][TILE_POS_DATA], temp2)
    end for

    -- 3. horizontal scan
    Scan[3][TILE_CLR_DATA] = PlayTable    -- that's easy
    for row = 1 to NROWS do
        temp2 = {}
        for col = 1 to NCOLS do
            temp2 = append(temp2, {row,col})
        end for
        Scan[3][TILE_POS_DATA] = append(Scan[3][TILE_POS_DATA],temp2)
    end for

    -- 4. vertical scan
    for col = 1 to NCOLS do

        temp = {}
        temp2 = {}

        for row = 1 to NROWS do
            temp = append(temp, PlayTable[row][col])
            temp2 = append(temp2, {row,col})
        end for

        Scan[4][TILE_CLR_DATA] = append(Scan[4][TILE_CLR_DATA],temp)
        Scan[4][TILE_POS_DATA] = append(Scan[4][TILE_POS_DATA],temp2)

    end for

    return Scan
end function -- GenScanPlayTable

function ScanForRunCtl(integer Protag, sequence Scan, integer run)
-- Searchs sequence Scan for a run of 'run' number of tiles, belonging
-- to Protag(onist).
-- Entry:  Protag  = ID of player for whom runs sought
--           Scan       = freshly generated scan sequence
--           run       = runs considered will have at least run number of tiles
-- Returns:  info about the run owners's ID number and the coords {row,col}
--           of the begining and end of the run, if a run found....
    sequence searcher, coord, runs_info
    integer run_entity,p

    searcher = repeat(Protag, run) -- what we are looking for

    -- do the search
    run_entity = False    -- assume no winner
    runs_info = {}
    coord = {0,0}

    for i = 1 to length(Scan) do

        for j = 1 to length(Scan[i][1]) do

            p = match(searcher,Scan[i][1][j])

            if p then
                run_entity = Protag
                coord[1] = Scan[i][2][j][p]
                coord[2] = Scan[i][2][j][p+run-1]
                runs_info = append(runs_info, {run_entity, coord})
            end if

        end for

    end for

    return runs_info
end function -- ScanForRunCtl

function SearchForRuns(integer run, sequence protagonists)
-- Search for run_entity having a run of at least length 'run'
-- Returns the first such found, else False if none found.
    sequence scan_seq, run_info
    integer run_entity

    -- generate scanning version of PlayTable
    scan_seq = GenScanPlayTable()

    for i = 1 to length(protagonists) do

        -- do the scan for each protagonist
        run_entity = False    -- none identified
        run_info = ScanForRunCtl(protagonists[i], scan_seq, run)

        if length(run_info) > 0 and run_info[1][1] then

            -- a run identified; get the identity and exit
            run_entity = run_info[1][1]
            run_entity = protagonists[i]    -- alternative, may be clearer
            exit

        end if

    end for

    return run_entity
end function -- SearchForRuns

procedure TestForGameOver()
-- A game is over if a run of 4 similar tiles
-- exists in PlayTable (diagonal,horizontal or
-- vertical), or if all tile spaces are filled.
    sequence msg, winner
    integer play_again, check_4

    check_4 = SearchForRuns(4,{HUMAN_ID,COMPUTER_ID})

    if check_4 then

        -- we have a winner
        GameIsOn = False                -- game is over

        -- identify and name winner
        if check_4 = COMPUTER_ID then

            winner = "COMPUTER"

        else

            winner = "YOU"

        end if

        -- announce the winner
        PlaySnd(CHIRP)
        msg = sprintf("Game Over - WINNER IS %s!",{winner})
        InfoMsg(msg, "Game Over")
        StopSnd()

    elsif MoveCounter >= MAX_MOVES then

        -- we have end of game with no winner
        GameIsOn = False

        -- announce no winner
        msg = "Game is over without a winner.\nPlay again?"

    end if

    if not GameIsOn then

        -- ask if play again
        msg = "Play another game?"
        play_again = AskMsg(msg,"End of Game")

        if play_again then

            InitializeNewGame()
        else

            GameIsOn = False
            TerminateProgram()

        end if

    end if

end procedure -- TestForGameOver

procedure DropTile(integer row_count, integer Col, integer Protag)
-- Graphically display falling tile; runs down column Col and briefly
-- display Protag's tile at each position until row row_count is
-- reached.
    integer row

    row = 0

    while row < row_count do

        row += 1

        -- display Protag's tile
        SetPicture(Tiles[row][Col], PictureButton, TILE_PICS[Protag])
        SetWinTimer(50)

        -- loop until time expired
        TimeDelayLoop()

        -- display the NULL_ID tile
        SetPicture(Tiles[row][Col], PictureButton, TILE_PICS[NULL_ID])

    end while

end procedure -- DropTile

function TryToAddTile(integer Column, integer Protag)
-- Try to add Protag's tile to the game at Column and
-- update PlayTable if tile is successfully added.
    integer AddSuccess, row_count

    -- find where tile will come to rest
    row_count = 0
    AddSuccess = False            -- assume column is full

    for row = 1 to NROWS do

        if PlayTable[row][Column] = 0 then

            row_count += 1
            AddSuccess = True     -- at least 1 spare space in the Column

        end if

    end for

    if AddSuccess then

        -- can add the move to the PlayTable and tile the display
        PlayTable[row_count][Column] = Protag

        -- visualise the fall of the tile
        DropTile(row_count,Column,Protag)

        -- show tile in its final position
        SetPicture(Tiles[row_count][Column], PictureButton, TILE_PICS[Protag])

    end if

    return AddSuccess
end function -- TryToAddTile

function TryExtending(sequence coord_info, integer run_len)
-- Entry:    coord_info = {{r1,c1}, {r2,c2}}
--                         {r1,c1} = coords of tile at start of run
--                         {r2,c2} = coords of tile at end of run
--               run_len = number of tiles in the run.
    integer move_success, row, col
    sequence delta_row_col, tester, msg

    if run_len <= 1 then

        -- invalid run_len; programmer error; give help
        msg = sprintf("In TryExtending(), run_len = %d.\n It must be > 1.",
                      run_len)
        WarnMsg(msg, "Programmer error - program will close.")
        TerminateProgram()

    else

        -- go ahead
        move_success = False    -- assume move will fail

        for i = 1 to length(coord_info) do

            -- compute where in PlayTable we have coords bracketing tile
            -- run specified by coord_info
            delta_row_col = (coord_info[i][2][2] - coord_info[i][2][1]) / (run_len - 1)
            -- coords in tester bracket the end tiles of the run of tiles
            tester = {coord_info[i][2][1]-delta_row_col,
                      coord_info[i][2][2]+delta_row_col}

            -- for each coord in tester
            for j = 1 to length(tester) do
                row = tester[j][1]
                col = tester[j][2]
                if row <= NROWS and row > 0 and col <= NCOLS and col > 0 then

                    -- we're inside the Tile array bounds
                    if PlayTable[row][col] = 0 then

                        -- try to play this column
                        -- ? {r,c} -- can activate to monitor ... (debugging)
                        -- move_success will be True if extending succeeds,
                        -- else it will be False.
                        move_success = TryToAddTile(col, COMPUTER_ID)
                        return move_success
                    end if

                end if

            end for

        end for

    end if

    return move_success
end function -- TryExtending

function TryToExtendRun(integer run_len, integer ProtagToExtend)
-- Attempts to extend a run of at least run_len tiles belonging to
-- ProtagToExtend (COMPUTER_ID or HUMAN_ID).
    sequence scan_seq, info
    integer move_success

    info = {}
    if SearchForRuns(run_len,{ProtagToExtend}) then

        -- have possible Protagonist to extend

        -- generate scan sequence
        scan_seq = GenScanPlayTable()

        -- gather info on the ProtagonistToExtend's runs
        info = ScanForRunCtl(ProtagToExtend, scan_seq, run_len)

        -- attempt extending
        move_success = TryExtending(info,run_len)

    else

        -- failed to to make a move
        move_success = False

    end if

    return move_success
end function -- TryToExtendRun

procedure MakeComputerMove()
-- Computes move on behalf of computer
    integer move_success, Col

    SetText(PromptText,"")

    -- computer's "thinking" time
    SetWinTimer(500)
    TimeDelayLoop()

    move_success = False

    while True do

        if TryToExtendRun(3,COMPUTER_ID) then

            -- computer has won!
            exit

        elsif TryToExtendRun(3,HUMAN_ID) then

            -- computer has blocked a human move
            exit

        elsif TryToExtendRun(2,COMPUTER_ID) then

            -- computer has developed a run
            exit

        else

            -- make a random move for computer
            while not move_success do

                -- try to add tile to randomly selected column
                Col = rand(NCOLS)

                -- will be True if addition succeeds
                if TryToAddTile(Col, COMPUTER_ID) then

                    exit

                end if

            end while

            -- make sure get out (this should never execute)
            exit

        end if

    end while

    MoveCounter += 1             -- move has been made; tally

    ActivePlayer = HUMAN_ID      -- toggle for next player
    TestForGameOver()            -- end game if there is a winner

    SetText(PromptText,PROMPT_TXT)

end procedure -- MakeComputerMove

procedure ClickScanTiles()
-- Scan all Tiles for a HUMAN_ID click
    sequence msg
    integer AddSuccess

    for Row = 1 to NROWS do

        for Col = 1 to NCOLS do

            if EventOwner = Tiles[Row][Col] then

                -- try to add a HUMAN_ID's tile
                AddSuccess = TryToAddTile(Col, HUMAN_ID)
                if AddSuccess then

                    -- successful move
                    MoveCounter += 1            -- tally another move
                    ActivePlayer = COMPUTER_ID    -- toggle for next player
                    TestForGameOver()            -- end game if there is a winner

                else

                    -- failed move ... announce it
                    PlaySnd(BOING)

                end if

            end if

        end for

    end for

end procedure -- ClickScanTiles

procedure CheckIfTerminateProgram()
-- Handles end of program run
    sequence msg

    if GameIsOn then

        msg = "A game is in progress.\n\nDo you really want to quit?"

        if AskMsg(msg,"End Program") then

            TerminateProgram()

        end if

    else

        TerminateProgram()

    end if

end procedure -- CheckIfTerminateProgram

procedure DisplayLogoDialog()

    SetPicture(LogoPicture,Picture,LOGO_PIC)
    SetVisible(LogoDialog,True)
    SetWinTimer(4000)
    TimeDelayLoop()
    SetVisible(LogoDialog,False)

end procedure -- DisplayLogoDialog
--***************************************************************************

--The Event Loop
procedure EventLoop()

--Run continuosly the event checking
while True do

  -- if it is COMPUTER_ID's move, make it
  if ActivePlayer = COMPUTER_ID then
      MakeComputerMove()
  end if

 --Wait for an event
 WaitEvent()

 --The event was a left mouse click?
 if Event = Click then

   -- scan for click on tile display (my interpolation in code here)
   if ActivePlayer = HUMAN_ID then
       ClickScanTiles()
   end if

   --Which control was the event Owner?
   if EventOwner = WinHwnd then
   elsif EventOwner = CreditsButton then
       ShowCredits2()
   elsif EventOwner = CloseButton then
       CheckIfTerminateProgram()

   end if

 end if

 --The default "close" button of a window has been clicked?
 if Event = Close then

   --Which window was the event Owner?
   if EventOwner = WinHwnd then
       CheckIfTerminateProgram()
   end if

 end if

end while

end procedure
--***************************************************************************

--The WinMain procedure
procedure WinMain()

    FontA = NewFont("FN_DEFAULT",12,1,0,0)

    --Enable "Close" events
    CloseEventEnabled = True

    --Create all windows, then their controls, and finally
    --set their fonts, icon, colors and the needed pictures
    WindowType = NoMaxWin
    ShowFlag = False
    Window("Connect-4",
           floor((ScreenWidth()-WINSIZE[1])/2),
           floor((ScreenHeight()-WINSIZE[2])/2),
           WINSIZE[1],WINSIZE[2])
    ShowFlag = True
    ConnectGroup = Control(Group,
                           "",
                           CONPOSN[1],CONPOSN[2],
                           CONSIZE[1],CONSIZE[2])
    PromptText = Control(Text,
                         "",
                         10,WINSIZE[2]-60,
                         220,15)
    CreditsButton = Control(Button,
                            "Credits",
                            WINSIZE[1]-190,WINSIZE[2]-60,
                            80,25)
    CloseButton = Control(Button,
                          "End Program",
                          WINSIZE[1]-100,WINSIZE[2]-60,
                          80,25)

    CreatePictureButtons()

    WindowType = NoTitleWin
    ShowFlag = False
    LogoDialog = Control(Dialog,"Logo",floor((ScreenWidth()-450)/2),floor((ScreenHeight()-368)/2),450,368)
    SetParentWindow(LogoDialog)
    ShowFlag = True
    LogoPicture = Control(Picture,"",0,0,450,387)

    -- set up to show credits
    InitialCredits2({RDS,EUWINGUI},1,2010,CL_RED)

    SetFont(PromptText,FontA)
    SetColor(PromptText, CL_BLACK, CL_DKYELLOW)

    DisplayLogoDialog()

    --Show the window(s)
    SetVisible(WinHwnd,True)

    -- start new game
    InitializeNewGame()

    --Run the event loop
    EventLoop()

    -- Free memory and close the application
    TerminateProgram()

end procedure
--***************************************************************************

--Start the application
WinMain()

-- End program listing --------------------------------------------------------
Conversion to HTML by PC2HTM.EXE