I tried to get back into Java by implementing a simplified version of the 2048 game. What I am mainly looking for is ways to reduce code duplication, all of my move functions have a very similar but distinct pattern.Suggestions on better patterns, practices or optimizations are welcome, keeping in mind that I am trying to implement my functionality without relying on any Libraries (built-in or otherwise).public class Game {    int[][] board;    int COLNUM = 4;    int ROWNUM = 4;    int globalPointer = 0;    // demonstration code to illustrate usage    public static void main (String[] args) {        Game game = new Game();        game.printBoard();        game.moveSouth();        game.printBoard();        game.moveWest();        game.printBoard();        game.moveWest();        game.printBoard();        game.moveEast();        game.printBoard();        game.moveNorth();        game.printBoard();    }    // simplified constructor, could add option to provide 2D array to instantiate the board    public Game() {        board = new int[ROWNUM][COLNUM];        board[0][0] = 2;        board[1][0] = 8;        board[2][0] = 4;        board[3][0] = 8;        board[0][1] = 1;        board[0][2] = 2;        board[0][3] = 1;        board[2][1] = 4;        board[2][2] = 4;        board[2][3] = 4;    }    public void printBoard() {        String output = ;        for (int i = 0; i < ROWNUM; i++) {            String row = ;            for (int j = 0; j < COLNUM; j++) {                row += board[i][j] + ,;            }            row += \n;            output += row;        }        System.out.println(output);    }    public void moveNorth() {        for (int col = 0; col < COLNUM; col++) {            globalPointer = 0;            for (int row = 0; row < ROWNUM; row++) {                // if not zero we try to suck in the next tile                if (board[row][col] != 0) {                    // if we had a prior zero tile then shift the column                    if (globalPointer <= row) {                        shiftRowTiles(row, col, false);                    }                }            }        }    }    public void moveSouth() {        for (int col = 0; col < COLNUM; col++) {            globalPointer = ROWNUM - 1;            for (int row = ROWNUM -1; row >= 0; row--) {                // if not zero we try to suck in the next tile                if (board[row][col] != 0) {                    // if we had a prior zero tile then shift the column                    if (globalPointer >= row) {                        shiftRowTiles(row, col, true);                    }                }            }        }    }    public void moveWest() {        for (int row = 0; row < ROWNUM; row++) {            globalPointer = 0;            for (int col = 0; col < COLNUM; col++) {                // if not zero we try to suck in the next tile                if (board[row][col] != 0) {                    // if we had a prior zero tile then shift the row                    if (globalPointer <= col) {                        shiftColTiles(row, col, false);                    }                 }            }        }    }    public void moveEast() {        for (int row = 0; row < ROWNUM; row++) {            globalPointer = COLNUM - 1;            for (int col = COLNUM - 1; col >= 0; col--) {                // if not zero we try to suck in the next tile                if (board[row][col] != 0) {                    // if we had a prior zero tile then shift the row                    if (globalPointer >= col) {                        shiftColTiles(row, col, true);                    }                 }            }        }    }    private void shiftRowTiles(int currentRow, int currentCol, boolean reverse) {        // if we have same tiles we suck them in           if (board[globalPointer][currentCol] == 0 || board[globalPointer][currentCol] == board[currentRow][currentCol]) {               if (currentRow > globalPointer || (reverse && (globalPointer > currentRow))) {                   board[globalPointer][currentCol] += board[currentRow][currentCol];                   board[currentRow][currentCol] = 0;               }           } else {               if (reverse) {                   globalPointer--;               } else {                   globalPointer++;               }               // look around to shift away zeroes in sequences               shiftRowTiles(currentRow, currentCol, reverse);           }       }    private void shiftColTiles(int currentRow, int currentCol, boolean reverse) {        // if we have same tiles we suck them in           if (board[currentRow][globalPointer] == 0 || board[currentRow][globalPointer] == board[currentRow][currentCol]) {               if (currentCol > globalPointer || (reverse && (globalPointer > currentCol))) {                   board[currentRow][globalPointer] += board[currentRow][currentCol];                   board[currentRow][currentCol] = 0;               }           } else {               if (reverse) {                   globalPointer--;               } else {                   globalPointer++;               }               // look around to shift away zeroes in sequences               shiftColTiles(currentRow, currentCol, reverse);           }       }}
Simplified version of 2048 game
java;2048