summaryrefslogtreecommitdiff
path: root/docs/chapter31/examples/TicTacToeGraphical.smp
blob: b4f8bcf48e7b41570bb20b2c4ca016dd40e6313b (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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Player is X, CPU is O. Squares are numbered left-to-right, top-to-bottom.
// Graphical tic-tac-toe; the same strategy as TicTacToe.smp.
// Click an empty square or press 1-9. R/Space replays; Q/Escape quits.
// Pass any argument for a five-poll window smoke test.
val main = { u8[~][] args ->
    val screen = new sys.draw.Canvas(600,780);
    val board = new u8[9];
    _reset(board);
    if( sys.gfx.open("Simple tic-tac-toe",screen.width,screen.height) != sys.gfx.OK ) return 1;
    sys.audio.open(); // Audio is optional; failure leaves the game silent.
    _draw(screen,board,-1);
    int result = sys.gfx.present(screen.pixels);
    bool mouseHeld = false, keyHeld = false, replayHeld = false;
    int hover = -1, frames = 0;
    while( result==sys.gfx.OK ) {
        result = sys.gfx.poll(true);
        if( result!=sys.gfx.OK ) break;
        if( sys.gfx.key('Q') ) break;
        bool down = sys.gfx.button(sys.gfx.LEFT);
        bool replay = sys.gfx.key('R') || sys.gfx.key(' ');
        int square = _square(sys.gfx.mouseX(),sys.gfx.mouseY());
        bool dirty = square!=hover;
        hover = square;
        if( replay && !replayHeld ) { sys.audio.stop(); _reset(board); dirty=true; }
        bool digit = false;
        int choice = down && !mouseHeld ? square : -1;
        for( int i=0; i<9; i++ ) {
            int key = '1'+i;
            if( sys.gfx.key(key) ) {
                digit = true;
                if( !keyHeld ) choice=i;
            }
        }
        if( !(replay && !replayHeld) && _move(board,choice) ) {
            sys.audio.play(_sound(board));
            dirty=true;
        }
        mouseHeld = down;
        keyHeld = digit;
        replayHeld = replay;
        if( dirty ) {
            _draw(screen,board,hover);
            result = sys.gfx.present(screen.pixels);
        }
        if( args#>1 && ++frames==5 ) break;
    }
    sys.audio.close();
    sys.gfx.close();
    return result==sys.gfx.ERROR ? 1 : 0;
};

// Called only for an accepted move; invalid input never requests a sound.
val _sound = { u8[~] board ->
    int winner = _winner(board);
    return winner=='X' ? sys.audio.WIN : winner=='O' ? sys.audio.LOSE : sys.audio.BOOP;
};

val _reset = { u8[] board ->
    for( int i=0; i<9; i++ ) board[i]=' ';
};
val _square = { int x, int y ->
    if( x<48 || x>=552 || y<168 || y>=672 ) return -1;
    return (y-168)/168*3+(x-48)/168;
};

// One player turn followed by one CPU turn. Invalid/finished moves do nothing.
val _move = { u8[] board, int square ->
    if( square<0 || square>=9 || board[square]!=' ' || _finished(board) ) return false;
    board[square] = 'X';
    if( !_finished(board) ) {
        int reply = _cpu(board);
        if( reply>=0 ) board[reply]='O';
    }
    return true;
};

val _finished = { u8[~] board ->
    if( _winner(board) ) return true;
    for( int i=0; i<9; i++ ) if( board[i]==' ' ) return false;
    return true;
};

val _winningLine = { 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 i;
        if( board[i]!=' ' && board[i]==board[i+3] && board[i]==board[i+6] ) return 3+i;
    }
    if( board[4]!=' ' ) {
        if( board[0]==board[4] && board[4]==board[8] ) return 6;
        if( board[2]==board[4] && board[4]==board[6] ) return 7;
    }
    return -1;
};
// Return the winning mark, or zero.
val _winner = { u8[~] board ->
    int line = _winningLine(board);
    return line<0 ? 0 : line<3 ? board[line*3] : line<6 ? board[line-3] : board[4];
};

// Try each empty square, restoring it before returning.
val _threat = { u8[] board, u8 mark ->
    for( int i=0; i<9; 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<9; i+=2 ) if( board[i]==' ' ) return i;
    // Take whatever is available
    for( int i=0; i<9; i++ ) if( board[i]==' ' ) return i;
    return -1;
};

val _draw = { !sys.draw.Canvas screen, u8[~] board, int hover ->
    u32 background = 0x142e36, paper = 0xf2eddf, ink = 0x35535a;
    u32 cross = 0xe47b57, ring = 0x248b89, accent = 0xf0c778;
    int winner = _winner(board);
    bool finished = _finished(board);
    screen.clear(background);
    screen.text(48,34,"TIC TAC TOE",6,paper);
    screen.fillRect(48,90,504,3,ink);
    screen.text(48,116,finished ? (winner=='X' ? "YOU WIN!" : winner=='O' ? "CPU WINS" : "DRAW") : "YOUR TURN",4,accent);
    screen.line(428,112,450,134,5,cross).line(450,112,428,134,5,cross);
    screen.circle(496,123,13,5,0x70c9bf);
    screen.text(428,145,"YOU",2,0xa2b9b7);
    screen.text(484,145,"CPU",2,0xa2b9b7);
    screen.fillRect(48,168,504,504,paper);
    if( !finished && hover>=0 && hover<9 && board[hover]==' ' )
        screen.fillRect(48+(hover-hover/3*3)*168,168+hover/3*168,168,168,0xd9e8de);
    for( int i=1; i<3; i++ ) {
        screen.fillRect(48+i*168-3,168,6,504,ink);
        screen.fillRect(48,168+i*168-3,504,6,ink);
    }
    for( int i=0; i<9; i++ ) {
        int cx = 132+(i-i/3*3)*168, cy = 252+i/3*168;
        if( board[i]=='X' )
            screen.line(cx-38,cy-38,cx+38,cy+38,12,cross).line(cx+38,cy-38,cx-38,cy+38,12,cross);
        else if( board[i]=='O' ) screen.circle(cx,cy,48,11,ring);
        else screen.text(cx-6,cy-10,i==0 ? "1" : i==1 ? "2" : i==2 ? "3" :
            i==3 ? "4" : i==4 ? "5" : i==5 ? "6" : i==6 ? "7" : i==7 ? "8" : "9",4,0x95a59e);
    }
    int line = _winningLine(board);
    if( line>=0 ) {
        int first = line<3 ? line*3 : line<6 ? line-3 : line==6 ? 0 : 2;
        int last = line<3 ? first+2 : line<6 ? first+6 : line==6 ? 8 : 6;
        screen.line(132+(first-first/3*3)*168,252+first/3*168,132+(last-last/3*3)*168,252+last/3*168,5,0xeab64e);
    }
    screen.text(48,704,"CLICK A SQUARE OR PRESS 1-9",3,paper);
    screen.text(48,738,"R OR SPACE REPLAY   Q OR ESC QUIT",3,0xa2b9b7);
    screen.rect(48,168,504,504,3,ink);
};