Thread: HarvardX's CS50 pset3 "Game of Fifteen" code misprinting log.txt

  1. #1
    Registered User
    Join Date
    Jun 2017
    Posts
    88

    HarvardX's CS50 pset3 "Game of Fifteen" code misprinting log.txt

    I am going through HarvardX's CS50 class. I am working on the last problem in "pset3." It is the Game of Fifteen (the slide puzzle game). I am instructed to write four functions which are called from main. The Harvard people have already written main. I have finished writing my four functions, and the game works great as far as I can tell. To help test that the program works, Harvard has written a test program named check50 which is run from the command line of their browser-accessed IDE. This program doesn't like what I've written. What it complains about is what seems to be a mismatch between its expected output and the log.txt file which is output by the main code they have written for the Game of Fifteen I am working on. I have a link to what this looks like:
    This is CS50 Check.
    You'll have to expand "init initializes 3x3 board correctly" to see what I mean.

    I don't believe I can do anything about the erroneous looking output on the left side of the screen. This is just what check50 comes up with, and I don't believe I have access to that. What I do believe I have access to is the log.txt output on the right side of the screen. I have looked over their code in main, and it looks fine as far as I can see. Why, when the board array is correctly printed to the screen, does the board array does not seem to sit inside the 3x3 area? What seems to happen is it just keeps on thinking the data is all on the first row, so it prints nothing on any row except the first.

    Code:
    /**
     * fifteen.c
     *
     * Implements Game of Fifteen (generalized to d x d).
     *
     * Usage: fifteen d
     *
     * whereby the board's dimensions are to be d x d,
     * where d must be in [DIM_MIN,DIM_MAX]
     *
     * Note that usleep is obsolete, but it offers more granularity than
     * sleep and is simpler to use than nanosleep; `man usleep` for more.
     */
     
    #define _XOPEN_SOURCE 500
    
    #include <cs50.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    // constants
    #define DIM_MIN 3
    #define DIM_MAX 9
    
    // board
    int board[DIM_MAX][DIM_MAX];
    
    // dimensions
    int d;
    
    // prototypes
    void clear(void);
    void greet(void);
    void init(int length, int board[][length]);
    void draw(int length, int board[][length]);
    bool move(int length, int board[][length], int);
    bool won(int length, int board[][length]);
    
    int main(int argc, string argv[])
    {
    
        
        // ensure proper usage
        if (argc != 2)
        {
            printf("Usage: fifteen d\n");
            return 1;
        }
    
        // ensure valid dimensions
        d = atoi(argv[1]);
        if (d < DIM_MIN || d > DIM_MAX)
        {
            printf("Board must be between %i x %i and %i x %i, inclusive.\n",
                DIM_MIN, DIM_MIN, DIM_MAX, DIM_MAX);
            return 2;
        }
    
        // open log
        FILE *file = fopen("log.txt", "w");
        if (file == NULL)
        {
            return 3;
        }
    
        // greet user with instructions
        greet();
    
        // initialize the board
        init(d, board);
    
        // accept moves until game is won
        while (true)
        {
            // clear the screen
            clear();
    
            // draw the current state of the board
            draw(d, board);
    
            // log the current state of the board (for testing)
            for (int i = 0; i < d; i++)
            {
                for (int j = 0; j < d; j++)
                {
                    fprintf(file, "%i", board[i][j]);
                    if (j < d - 1)
                    {
                        fprintf(file, "|");
                    }
                }
                fprintf(file, "\n");
            }
            fflush(file);
    
            // check for win
            if (won(d, board))
            {
                printf("ftw!\n");
                break;
            }
    
            // prompt for move
            printf("Tile to move: ");
            int tile = get_int();
            
            // quit if user inputs 0 (for testing)
            if (tile == 0)
            {
                break;
            }
    
            // log move (for testing)
            fprintf(file, "%i\n", tile);
            fflush(file);
    
            // move if possible, else report illegality
            if (!move(d, board, tile))
            {
                printf("\nIllegal move.\n");
                usleep(120000);
            }
    
            // sleep thread for animation's sake
            usleep(120000);
        }
        
        // close log
        fclose(file);
    
        // success
        return 0;
    }
    
    /**
     * Clears screen using ANSI escape sequences.
     */
    void clear(void)
    {
        printf("\033[2J");
        // printf("\033[%d;%dH", 0, 0);
    }
    
    /**
     * Greets player.
     */
    void greet(void)
    {
        clear();
        printf("WELCOME TO GAME OF FIFTEEN\n");
        usleep(500000);
    }
    
    /**
     * Initializes the game's board with tiles numbered 1 through d*d - 1
     * (i.e., fills 2D array with values but does not actually print them).  
     */
     // parameters: board is a pointer to the first element of the array
     //             length is the length of the array
    void init(int length, int board[][length])
    {
        // an incrementing tileNumber is assigned to the array elements
        int tileNumber = 0;
        int column;
        for(int row = length - 1; row >= 0; row--)
        {
            for(column = length - 1; column >= 0; column--)
            {
                board[row][column] = tileNumber;
                tileNumber++;
            }
        }
        // to retain a solvable puzzle, tileNumbers 1 and 2 are swapped in even puzzels
        if(length % 2 == 0)
        {
            board[length - 1][length - 2] = 2;
            board[length - 1][length - 3] = 1;
        }
    }
    
    /**
     * Prints the board in its current state.
     */
    void draw(int length, int board[][length])
    {
        int column;
        for(int row = 0; row < length; row++)
        {
            for(column = 0; column < length; column++)
            {
                if(column == 0)
                {
                    printf("\n");
                }
                if(board[row][column] >= 10)
                {
                    printf("%i ", board[row][column]);
                }
                else if(board[row][column] > 0)
                {
                    printf(" %i ", board[row][column]);
                }
                else
                {
                    printf(" _ ");
                }
            }
        }
        printf("\n");
    }
    
    /**
     * If tile borders empty space, moves tile and returns true, else
     * returns false. 
     */
    bool move(int length, int board[][length], int moveTileValue)
    {
        int moveTileRow = -1;
        int moveTileColumn;
        int column;
        // find location of moveTileValue
        for(int row = 0; row < length && moveTileRow < 0; row++)
        {
            for(column = 0; column < length && moveTileRow < 0; column++)
            {
                if(board[row][column] == moveTileValue)
                {
                    moveTileRow = row;
                    moveTileColumn = column;
                }
            }
        }
        
        // if move tile can be moved, do so and return true
        if(moveTileRow > 0             && board[moveTileRow - 1][moveTileColumn] == 0)
        {
            board[moveTileRow - 1][moveTileColumn] = moveTileValue;
            board[moveTileRow][moveTileColumn] = 0;
            return true;
        }
        if(moveTileRow < length - 1    && board[moveTileRow + 1][moveTileColumn] == 0)
        {
            board[moveTileRow + 1][moveTileColumn] = moveTileValue;
            board[moveTileRow][moveTileColumn] = 0;
            return true;
        }
        if(moveTileColumn > 0          && board[moveTileRow][moveTileColumn - 1] == 0)
        {
            board[moveTileRow][moveTileColumn - 1] = moveTileValue;
            board[moveTileRow][moveTileColumn] = 0;
            return true;
        }
        if(moveTileColumn < length - 1 && board[moveTileRow][moveTileColumn + 1] == 0)
        {
            board[moveTileRow][moveTileColumn + 1] = moveTileValue;
            board[moveTileRow][moveTileColumn] = 0;
            return true;
        }
        // move tile cannot be moved, so return false
        return false;
    }
    
    /**
     * Returns true if game is won (i.e., board is in winning configuration), 
     * else false.
     */
    bool won(int length, int board[][length])
    {
        int number = 0;
        int column;
        for(int row = 0; row < length; row++)
        {
            for(column = 0; column < length; column++)
            {
                number++;
                if(board[row][column] != number && number != length * length)
                {
                    return false;
                }
            }
        }
        return true;
    }
    Last edited by jack jordan; 06-08-2017 at 11:10 PM.

  2. #2
    Registered User
    Join Date
    Jun 2017
    Posts
    88

    Solved.

    I suppose I don't really understand why, but changing
    Code:
    int board[DIM_MAX][DIM_MAX]
    to
    Code:
    int board[d][d]
    and moving it inside main underneath where d is assigned a command line argument here:
    Code:
    // ensure valid dimensions
        d = atoi(argv[1]);
        if (d < DIM_MIN || d > DIM_MAX)
        {
            printf("Board must be between %i x %i and %i x %i, inclusive.\n",
                DIM_MIN, DIM_MIN, DIM_MAX, DIM_MAX);
            return 2;
        }
    solves all the problems.
    Last edited by jack jordan; 06-08-2017 at 11:27 PM.

  3. #3
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,661
    > void init(int length, int board[][length])
    Well when you lie to the code, you get bad results.

    The real size of the array is
    int board[DIM_MAX][DIM_MAX];

    I hope you also localised 'd' inside main as well, instead of relying on global variables.

    > * Note that usleep is obsolete, but it offers more granularity than
    > * sleep and is simpler to use than nanosleep; `man usleep` for more.
    I notice from your compiler log that usleep is effectively made a 'nop' with
    clang -D__useconds_t=long -Dusleep=fabs -ggdb3 -O0 -std=c11 -Wall -Werror -o fifteen fifteen.c -lcs50 -lm.
    But I suppose that's fine for automated testing, instead of interactive play.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  4. #4
    Registered User
    Join Date
    Jun 2015
    Posts
    1,640
    You're mixing two different ways of defining your array. One is giving it a static size determined at compile time. The other is to dynamically allocate it as a "variable-length" array. Your initial code defines the array as statically-sized but passes it around as if it's variable-length.

    As for the statically-sized array, even if you end up using only part of it, the whole array still exists. If you created the array with a max 5x5 size and stored a 3x3 array in it, then the array data would look something like this in memory.
    Code:
    1 2 3 x x         x represents the unused elements
    4 5 6 x x
    7 8 9 x x
    x x x x x
    x x x x x
    So the 2nd row starts 5 elements after the first element of the first row even though we're only using 3 elements of each row. But if you tell the compiler that it's really a 3x3 array (and not just a 3x3 array ensconced in a 5x5 array) then it'll look 3 elements after the 1st element of the 1st row to find the 2nd row, but find instead some of the unused data of the larger array, so it'll write code that will decipher the rows as 1,2,3; x,x,4; 5,6,x

    Code:
    #include <stdio.h>
    
    #define SIZE 5
    
    // print given sub-array of fixed-sized array.
    void f(int a[][SIZE], int sz) {
        for (int r=0; r<sz; r++) {
            for (int c=0; c<sz; c++)
                printf("%2d ", a[r][c]);
            putchar('\n');
        }
        putchar('\n');
    }
    
    // print variable-length array
    void g(int sz, int b[][sz]) {
        for (int r=0; r<sz; r++) {
            for (int c=0; c<sz; c++)
                printf("%2d ", b[r][c]);
            putchar('\n');
        }
        putchar('\n');
    }
    
    int main() {
        int a[SIZE][SIZE] = {
            {1, 2, 3, 90, 91},
            {4, 5, 6, 92, 93},
            {7, 8, 9, 94, 95}
        };
        f(a, 3);
        f(a, 5);
        g(3, a);
    
        printf("size: ");
        int sz;
        scanf("%d", &sz);
        int b[sz][sz];
        for (int r=0; r<sz; r++)
            for (int c=0; c<sz; c++)
                b[r][c] = (r+1) * (c+1);
        f(b, sz);
        g(sz, b);
    
        return 0;
    }
    
    Output:
    
     1  2  3         // properly printed
     4  5  6 
     7  8  9 
    
     1  2  3 90 91     // the entire larger array
     4  5  6 92 93 
     7  8  9 94 95 
     0  0  0  0  0 
     0  0  0  0  0 
    
     1  2  3      // this is the kind of thing that was happening in your program
    90 91  4 
     5  6 92 
    
    size: 3          // variable-length array
     1  2  3               // trying to print it as fixed-sized (5x5)
     6  3  6 
    4195988  0  0 
    
     1  2  3 
     2  4  6 
     3  6  9
    Last edited by algorism; 06-09-2017 at 12:17 AM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 1
    Last Post: 05-16-2016, 11:39 AM
  2. Plz help me to include cs50.h and cs50.c into Code::Block
    By Huncowboy in forum C Programming
    Replies: 4
    Last Post: 03-09-2010, 10:05 AM
  3. An interesting code about "struct" in "union"
    By meili100 in forum C++ Programming
    Replies: 3
    Last Post: 04-08-2008, 04:37 AM
  4. "itoa"-"_itoa" , "inp"-"_inp", Why some functions have "
    By L.O.K. in forum Windows Programming
    Replies: 5
    Last Post: 12-08-2002, 08:25 AM
  5. "CWnd"-"HWnd","CBitmap"-"HBitmap"...., What is mean by "
    By L.O.K. in forum Windows Programming
    Replies: 2
    Last Post: 12-04-2002, 07:59 AM

Tags for this Thread