summaryrefslogtreecommitdiff
path: root/chapter30/docs/examples/TicTacToe.smp
blob: a710e440a9f6c4a8f7a2fb69560a7ca8bda08686 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Player is X, CPU is O. Squares are numbered left-to-right, top-to-bottom.
val main = { u8[~][] args ->
    sys.io.p("Tic-tac-toe!\nYou are X; the CPU is O.  You go first.\n");
    sys.io.p("Press a square's number (no Enter needed), or q/Esc to quit.\n");
    sys.io.p("Squares:\n 1 | 2 | 3 \n---+---+---\n 4 | 5 | 6 \n---+---+---\n 7 | 8 | 9 \n\n");
    val board = new u8[9];
    for( int i=0; i<board#; i++ ) board[i]=' ';
    // Draw empty board
    _draw(board);

    while( true ) {
        sys.io.p("Your move (1-9): ");
        // Valid user move 1-9, or 0 for quitting
        int square = _validMove(board) - 1;
        if( square < 0 ) return 0;
        // Set user choice; print board
        board[square] = 'X';
        sys.io.p("\n");
        _draw(board);
        // A winner?
        if( _finished(board) ) return 0;
        // Compute CPU move
        int reply = _cpu(board);
        // Set CPU choice; print board
        board[reply]='O';
        sys.io.p("CPU plays %(reply+1).\n");
        _draw(board);
        // A winner?
        if( _finished(board) ) return 0;
    }
};

// Return with a valid user move 1-9, or 0 to exit
val _validMove =  { u8[~] board ->
    while( true ) {
        int key = sys.io.getch();
        if( key == -1 && sys.libc.errno() != sys.libc.EOF ) {
            sys.io.err("\nUnable to read the keyboard.\n");
            return 0;
        }
        // Various quitting conditions
        if( key == -1 || key == 'q' || key == 'Q' || key == 27 || key == 3 || key == 4 ) {
            sys.io.p("\nGoodbye!\n");
            return 0;
        }
        // Ignore CR/LF
        if( key == '\r' || key == '\n' ) continue;

        // Valid choice
        if( key < '1' || key > '9' ) {
            sys.io.p("\nChoose a square from 1 to 9: ");
            continue;
        }
        if( board[key-'1'] != ' ' ) {
            sys.io.p("\nThat square is occupied. Your move (1-9): ");
            continue;
        }
        sys.io.p("%c(key)");
        return key-'0';
    }
};

// Return the winning mark, or zero.
val _winner = { u8[~] board ->
    for( int i=0; i<3; i++ ) {
        int row = i*3;
        if( board[row]!=' ' && board[row]==board[row+1] && board[row]==board[row+2] )
            return board[row];
        if( board[i]!=' ' && board[i]==board[i+3] && board[i]==board[i+6] )
            return board[i];
    }
    if( board[4]!=' ' ) {
        if( board[0]==board[4] && board[4]==board[8] ) return board[4];
        if( board[2]==board[4] && board[4]==board[6] ) return board[4];
    }
    return 0;
};

val _finished = { u8[~] board ->
    int winner = _winner(board);
    if( winner=='X' ) { sys.io.p("You win!\n"); return true; }
    if( winner=='O' ) { sys.io.p("CPU wins!\n"); return true; }
    for( int i=0; i<board#; i++ )
        if( board[i]==' ' ) return false;
    sys.io.p("It's a draw!\n");
    return true;
};

// Try each empty square, restoring it before returning.
val _threat = { u8[] board, u8 mark ->
    for( int i=0; i<board#; i++ ) {
        if( board[i]==' ' ) {
            board[i]=mark;
            bool wins = _winner(board)==mark;
            board[i]=' ';       // Restore board
            if( wins ) return i;
        }
    }
    return -1;
};

// Win if possible, otherwise block, then prefer center, corners, and edges.
// Called only after _finished has ruled out a full board.
val _cpu = { u8[] board ->
    // If O can win, do so
    int win = _threat(board,'O');
    if( win>=0 ) return win;
    // If X will win next move, block
    int block = _threat(board,'X');
    if( block>=0 ) return block;
    // If center is open, take it
    if( board[4]==' ' ) return 4;
    // If a corner is open, take it
    for( int i=0; i<board#; i+=2 )
        if( board[i]==' ' ) return i;
    // Take whatever is available
    for( int i=0; i<board#; i++ )
        if( board[i]==' ' ) return i;
    return -1;
};

val _draw = { u8[~] board ->
    val out = new sys.aryu8();
    for( int row=0; row<3; row++ ) {
        for( int col=0; col<3; col++ ) {
            out.add(' ').add(board[row*3+col]).add(' ');
            if( col<2 ) out.add('|');
        }
        out.add('\n');
        if( row<2 ) out.addAry("---+---+---\n");
    }
    out.write(sys.libc.STDOUT);
};