Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
zmx0142857
GitHub Repository: zmx0142857/mini-games
Path: blob/master/py/tictactoe.py
363 views
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
4
"""tic tac toe"""
5
6
from game import Console
7
from time import sleep
8
from random import choice
9
import sys
10
11
class SortedSet(list):
12
def __in__(self, elem):
13
return self.bsearch(elem)[0]
14
15
def append(self, elem):
16
# do nothing if elem in self
17
if elem not in self:
18
list.append(self, elem)
19
i = len(self)-2
20
while i >= 0 and self[i] > elem:
21
self[i+1] = self[i]
22
i -= 1
23
self[i+1] = elem
24
25
def remove(self, elem):
26
# do nothing if elem not in self
27
isin, i = self.bsearch(elem)
28
size = len(self)
29
if isin:
30
while i < size-1:
31
self[i] = self[i+1]
32
i += 1
33
self.pop()
34
35
def bsearch(self, elem):
36
size = len(self)
37
if size == 0:
38
return False
39
lo = 0
40
hi = size-1
41
while lo < hi:
42
mid = (lo+hi) >> 1
43
if elem > self[mid]:
44
lo = mid+1
45
elif elem < self[mid]:
46
hi = mid-1
47
else:
48
return True, mid
49
return elem == self[lo], lo
50
51
def two_sum(self, elem):
52
i = 0
53
j = len(self)-1
54
while i < j:
55
if self[i] + self[j] < elem:
56
i += 1
57
elif self[i] + self[j] > elem:
58
j -= 1
59
else:
60
return True, elem, i, j
61
return False
62
63
# ---- class SortedSet ends
64
65
class Board:
66
def __init__(self, level):
67
self.x = 'x'
68
self.o = 'o'
69
self.empty = '.'
70
self.value = [
71
[4, 9, 2],
72
[3, 5, 7],
73
[8, 1, 6]
74
]
75
self.pos = [ None,
76
(2, 1), (0, 2), (1, 0),
77
(0, 0), (1, 1), (2, 2),
78
(1, 2), (2, 0), (0, 1)
79
]
80
self.board = [
81
[self.empty for j in range(3)] for i in range(3)
82
]
83
self.step = 1
84
self.available = SortedSet(i for i in range(1, 10))
85
self.taken = [SortedSet(), SortedSet()]
86
self.last = None
87
self.gameover = None
88
if level == 0:
89
self.ai = self.ai0
90
elif level == 2:
91
self.ai = self.ai2
92
else:
93
self.ai = self.ai1
94
95
def __str__(self):
96
return '\n'.join(
97
' '.join(token for token in line) for line in self.board)
98
99
def token(self, step=None):
100
if step == None:
101
step = self.step
102
return self.o if step & 1 else self.x # test mod 2
103
104
def info(self):
105
return '%s: ' % self.token()
106
107
def take(self, n):
108
if n not in self.available:
109
raise ValueError('position taken')
110
111
if self.last != None:
112
i, j = self.pos[self.last]
113
self.board[i][j] = self.board[i][j][4]
114
i, j = self.pos[n]
115
self.board[i][j] = '\033[7m%s\033[0m' % self.token()
116
self.last = n
117
self.gameover = self.is_gameover()
118
self.taken[self.step % 2].append(self.last)
119
self.available.remove(self.last)
120
self.step += 1
121
122
def is_gameover(self):
123
if self.taken[self.step % 2].two_sum(15-self.last):
124
return '%s wins!' % self.token(self.step)
125
elif self.step == 9:
126
return 'draw!'
127
return None
128
129
def ai0(self):
130
ret = choice(self.available)
131
self.take(ret)
132
return ret
133
134
def ai1(self):
135
# make his
136
for n in self.available:
137
if self.taken[self.step % 2].two_sum(15-n):
138
self.take(n)
139
return n
140
# stuck yours
141
for n in self.available:
142
if self.taken[(self.step-1) % 2].two_sum(15-n):
143
self.take(n)
144
return n
145
return self.ai0()
146
147
def ai2(self):
148
if 5 in self.available:
149
self.take(5)
150
return 5
151
if self.step == 2:
152
n = choice([2, 4, 6, 8])
153
self.take(n)
154
return n
155
if self.step == 3 and self.taken[(self.step-1) % 2][0] & 1:
156
n = choice([i for i in self.available if i % 2 == 0])
157
self.take(n)
158
return n
159
return self.ai1()
160
161
# ---- class Board ends
162
163
class Game:
164
def __init__(self, mode=1, level=1):
165
try:
166
self.mode = int(mode)
167
self.level = int(level)
168
if self.mode < 0 or self.mode > 3:
169
raise ValueError
170
if self.level < 0 or self.level > 2:
171
raise ValueError
172
except Exception:
173
print('invalid arguments.\n'
174
'usage: tictactoe.py <mode> <level>\n'
175
'mode = 0 (pvp), 1 (pvc), 2 (cvp), 3 (cvc)\n'
176
'level = 0, 1, 2')
177
exit(1)
178
self.console = Console(line_msg=4, line_input=5)
179
self.board = Board(level)
180
181
def player_move(self):
182
try:
183
s = self.console.input(self.board.info())
184
except EOFError:
185
if self.console.yes('quit game?'):
186
exit()
187
return False
188
try:
189
line, col = map(int, s.split())
190
except ValueError:
191
self.console.msg('invalid input')
192
return False
193
try:
194
n = self.board.value[line-1][col-1]
195
except IndexError:
196
self.console.msg('index out of range')
197
return False
198
try:
199
self.board.take(n)
200
except ValueError as e:
201
self.console.msg(e)
202
return False
203
return True
204
205
def computer_move(self):
206
n = self.board.ai()
207
line, col = self.board.pos[n]
208
sleep(0.2)
209
self.console.msg("computer takes %d %d" % (line+1, col+1))
210
211
def gameover(self):
212
self.console.cursor_goto(0)
213
print(self.board)
214
if self.board.gameover:
215
self.console.msg(self.board.gameover)
216
return True
217
return False
218
219
def pvp(self):
220
while True:
221
if not self.player_move():
222
continue
223
if self.gameover():
224
break
225
226
def pvc(self):
227
while True:
228
if not self.player_move():
229
continue
230
if self.gameover():
231
break
232
self.computer_move()
233
if self.gameover():
234
break
235
236
def cvp(self):
237
while True:
238
self.computer_move()
239
if self.gameover():
240
break
241
while not self.player_move():
242
pass
243
if self.gameover():
244
break
245
246
def cvc(self):
247
while True:
248
self.computer_move()
249
if self.gameover():
250
break
251
252
def play(self):
253
while True:
254
self.board = Board(self.level)
255
self.console.screen_clear()
256
print(self.board)
257
if self.mode == 0:
258
self.pvp()
259
elif self.mode == 1:
260
self.pvc()
261
elif self.mode == 2:
262
self.cvp()
263
elif self.mode == 3:
264
self.cvc()
265
if not self.console.yes('new game? '):
266
break
267
268
if __name__ == '__main__':
269
game = Game(*sys.argv[1:])
270
game.play()
271
272