require "curses"
include Curses
class Playfield < Window
def draw_vertical_line(x, y_start, y_finish)
for y in y_start..y_finish
setpos(y, x)
addch '|'
end
end
def draw_horizontal_line(y, x_start, x_finish)
for x in x_start..x_finish
setpos(y, x)
addch '_'
setpos(y, x + 7)
addch '_'
setpos(y, x + 14)
addch '_'
end
end
def move_cursor(ch, pos)
case ch
when "\167"
pos[:y] -= 3 if pos[:y] > 1
setpos(pos[:y], pos[:x])
when "\163"
pos[:y] += 3 if pos[:y] < 7
setpos(pos[:y], pos[:x])
when "\141"
pos[:x] -= 7 if pos[:x] > 3
setpos(pos[:y], pos[:x])
when "\144"
pos[:x] += 7 if pos[:x] < 17
setpos(pos[:y], pos[:x])
end
end
def place_mark(mark, marks, pos)
if inch == ' '.ord
addch(mark)
marks.each do |line|
line.each do |element|
element[:value] = mark if element[:y] == pos[:y] && element[:x] == pos[:x]
end
end
setpos(pos[:y], pos[:x])
return 'OK'
else
return nil
end
end
def check_marks(marks, mark)
# horizontal
marks.each do |line|
return 'win' if line.count{|element| element[:value] == mark} == 3
end
# vertical
transposed_marks = marks.transpose
transposed_marks.each do |column|
return 'win' if column.count{|element| element[:value] == mark} == 3
end
# diagonal
return 'win' if marks[0][0][:value] == mark && marks[1][1][:value] == mark && marks[2][2][:value] == mark
return 'win' if marks[2][0][:value] == mark && marks[1][1][:value] == mark && marks[0][2][:value] == mark
# draw?
counter = 0
marks.each do |line|
line.each { |element| counter += 1 if element[:value] == ' ' }
end
return "draw" if counter == 0
return nil
end
end
init_screen
crmode
noecho
# controls
message = "Press W, A, S, D to navigate the cursor, press Space to place your mark."
c = Window.new(1, message.length, lines/2 + 11, 0)
c.addstr(message)
c.refresh
# hints
h = Window.new(3, 38, lines/2 + 6, cols/2 - 18)
h.addstr "player 1's turn".ljust(38)
h.refresh
# playfield
p = Playfield.new(9, 20, lines/2 - 5, cols/2 - 9)
# draw grid
p.draw_vertical_line(6, 0, 8)
p.draw_vertical_line(13, 0, 8)
p.draw_horizontal_line(2, 0, 5)
p.draw_horizontal_line(5, 0, 5)
# kostyl
cursor_pos = { y: 4, x: 10 }
p.setpos(cursor_pos[:y], cursor_pos[:x])
# array of marks with coordinates
y = [1, 4, 7]
x = [3, 10, 17]
marks = Array.new(3) { Array.new(3) }
marks.each_index do |line|
marks[line].each_index do |column|
marks[line][column] = { y: y[line], x: x[column], value: ' ' }
end
end
# game part
mark = []
if rand(1..2) == 1
mark[1] = 'X'
mark[2] = 'O'
else
mark[1] = 'O'
mark[2] = 'X'
end
turn = 1 # 1st player's turn
begin
begin
ch = p.getch
p.move_cursor(ch, cursor_pos)
end until ch == ?\s
if p.place_mark(mark[turn], marks, cursor_pos)
result = p.check_marks(marks, mark[turn])
unless result
h.setpos(0, 0)
if turn == 1
turn = 2
h.addstr "player #{turn}'s turn".rjust(38)
else
turn = 1
h.addstr "player #{turn}'s turn".ljust(38)
end
h.refresh
end
end
end until result
h.setpos(0, 0)
if result == "draw"
h.addstr "DRAW!".center(38)
else
h.addstr "PLAYER #{turn} WINS!".center(38)
end
h.setpos(2, 0)
h.addstr "Press any key to exit.".center(38)
h.refresh
p.getch
p.close
close_screen
cmVxdWlyZSAiY3Vyc2VzIgppbmNsdWRlIEN1cnNlcwoKY2xhc3MgUGxheWZpZWxkIDwgV2luZG93CgoJZGVmIGRyYXdfdmVydGljYWxfbGluZSh4LCB5X3N0YXJ0LCB5X2ZpbmlzaCkKCQlmb3IgeSBpbiB5X3N0YXJ0Li55X2ZpbmlzaAoJCQlzZXRwb3MoeSwgeCkKCQkJYWRkY2ggJ3wnCgkJZW5kCgllbmQKCglkZWYgZHJhd19ob3Jpem9udGFsX2xpbmUoeSwgeF9zdGFydCwgeF9maW5pc2gpCgkJZm9yIHggaW4geF9zdGFydC4ueF9maW5pc2gKCQkJc2V0cG9zKHksIHgpCgkJCWFkZGNoICdfJwoJCQlzZXRwb3MoeSwgeCArIDcpCgkJCWFkZGNoICdfJwoJCQlzZXRwb3MoeSwgeCArIDE0KQoJCQlhZGRjaCAnXycKCQllbmQKCWVuZAoKCWRlZiBtb3ZlX2N1cnNvcihjaCwgcG9zKQoJCWNhc2UgY2gKCQl3aGVuICJcMTY3IgoJCQlwb3NbOnldIC09IDMgaWYgcG9zWzp5XSA+IDEKCQkJc2V0cG9zKHBvc1s6eV0sIHBvc1s6eF0pCgkJd2hlbiAiXDE2MyIKCQkJcG9zWzp5XSArPSAzIGlmIHBvc1s6eV0gPCA3CgkJCXNldHBvcyhwb3NbOnldLCBwb3NbOnhdKQoJCXdoZW4gIlwxNDEiCgkJCXBvc1s6eF0gLT0gNyBpZiBwb3NbOnhdID4gMwoJCQlzZXRwb3MocG9zWzp5XSwgcG9zWzp4XSkKCQl3aGVuICJcMTQ0IgoJCQlwb3NbOnhdICs9IDcgaWYgcG9zWzp4XSA8IDE3CgkJCXNldHBvcyhwb3NbOnldLCBwb3NbOnhdKQoJCWVuZAoJZW5kCgoJZGVmIHBsYWNlX21hcmsobWFyaywgbWFya3MsIHBvcykKCQlpZiBpbmNoID09ICcgJy5vcmQKCQkJYWRkY2gobWFyaykKCQkJbWFya3MuZWFjaCBkbyB8bGluZXwgCgkJCQlsaW5lLmVhY2ggZG8gfGVsZW1lbnR8CgkJCQkJZWxlbWVudFs6dmFsdWVdID0gbWFyayBpZiBlbGVtZW50Wzp5XSA9PSBwb3NbOnldICYmIGVsZW1lbnRbOnhdID09IHBvc1s6eF0KCQkJCWVuZAoJCQllbmQKCQkJc2V0cG9zKHBvc1s6eV0sIHBvc1s6eF0pCgkJCXJldHVybiAnT0snCgkJZWxzZQoJCQlyZXR1cm4gbmlsCgkJZW5kCgllbmQKCglkZWYgY2hlY2tfbWFya3MobWFya3MsIG1hcmspCgkJIyBob3Jpem9udGFsCgkJbWFya3MuZWFjaCBkbyB8bGluZXwKCQkJcmV0dXJuICd3aW4nIGlmIGxpbmUuY291bnR7fGVsZW1lbnR8IGVsZW1lbnRbOnZhbHVlXSA9PSBtYXJrfSA9PSAzCgkJZW5kCgoJCSMgdmVydGljYWwKCQl0cmFuc3Bvc2VkX21hcmtzID0gbWFya3MudHJhbnNwb3NlCgkJdHJhbnNwb3NlZF9tYXJrcy5lYWNoIGRvIHxjb2x1bW58CgkJCXJldHVybiAnd2luJyBpZiBjb2x1bW4uY291bnR7fGVsZW1lbnR8IGVsZW1lbnRbOnZhbHVlXSA9PSBtYXJrfSA9PSAzCgkJZW5kCgoJCSMgZGlhZ29uYWwKCQlyZXR1cm4gJ3dpbicgaWYgbWFya3NbMF1bMF1bOnZhbHVlXSA9PSBtYXJrICYmIG1hcmtzWzFdWzFdWzp2YWx1ZV0gPT0gbWFyayAmJiBtYXJrc1syXVsyXVs6dmFsdWVdID09IG1hcmsKCQlyZXR1cm4gJ3dpbicgaWYgbWFya3NbMl1bMF1bOnZhbHVlXSA9PSBtYXJrICYmIG1hcmtzWzFdWzFdWzp2YWx1ZV0gPT0gbWFyayAmJiBtYXJrc1swXVsyXVs6dmFsdWVdID09IG1hcmsKCgkJIyBkcmF3PwoJCWNvdW50ZXIgPSAwCgkJbWFya3MuZWFjaCBkbyB8bGluZXwKCQkJbGluZS5lYWNoIHsgfGVsZW1lbnR8IGNvdW50ZXIgKz0gMSBpZiBlbGVtZW50Wzp2YWx1ZV0gPT0gJyAnIH0KCQllbmQKCQlyZXR1cm4gImRyYXciIGlmIGNvdW50ZXIgPT0gMAoKCQlyZXR1cm4gbmlsCgllbmQKCmVuZAoKCmluaXRfc2NyZWVuCmNybW9kZQpub2VjaG8KCiMgY29udHJvbHMKbWVzc2FnZSA9ICJQcmVzcyBXLCBBLCBTLCBEIHRvIG5hdmlnYXRlIHRoZSBjdXJzb3IsIHByZXNzIFNwYWNlIHRvIHBsYWNlIHlvdXIgbWFyay4iCmMgPSBXaW5kb3cubmV3KDEsIG1lc3NhZ2UubGVuZ3RoLCBsaW5lcy8yICsgMTEsIDApCmMuYWRkc3RyKG1lc3NhZ2UpCmMucmVmcmVzaAoKIyBoaW50cwpoID0gV2luZG93Lm5ldygzLCAzOCwgbGluZXMvMiArIDYsIGNvbHMvMiAtIDE4KQpoLmFkZHN0ciAicGxheWVyIDEncyB0dXJuIi5sanVzdCgzOCkKaC5yZWZyZXNoCgoKIyBwbGF5ZmllbGQKcCA9IFBsYXlmaWVsZC5uZXcoOSwgMjAsIGxpbmVzLzIgLSA1LCBjb2xzLzIgLSA5KQoKIyBkcmF3IGdyaWQKcC5kcmF3X3ZlcnRpY2FsX2xpbmUoNiwgMCwgOCkKcC5kcmF3X3ZlcnRpY2FsX2xpbmUoMTMsIDAsIDgpCnAuZHJhd19ob3Jpem9udGFsX2xpbmUoMiwgMCwgNSkKcC5kcmF3X2hvcml6b250YWxfbGluZSg1LCAwLCA1KQoKIyBrb3N0eWwKY3Vyc29yX3BvcyA9IHsgeTogNCwgeDogMTAgfQpwLnNldHBvcyhjdXJzb3JfcG9zWzp5XSwgY3Vyc29yX3Bvc1s6eF0pCgojIGFycmF5IG9mIG1hcmtzIHdpdGggY29vcmRpbmF0ZXMKeSA9IFsxLCA0LCA3XQp4ID0gWzMsIDEwLCAxN10KbWFya3MgPSBBcnJheS5uZXcoMykgeyBBcnJheS5uZXcoMykgfQptYXJrcy5lYWNoX2luZGV4IGRvIHxsaW5lfCAKCW1hcmtzW2xpbmVdLmVhY2hfaW5kZXggZG8gfGNvbHVtbnwKCQltYXJrc1tsaW5lXVtjb2x1bW5dID0geyB5OiB5W2xpbmVdLCB4OiB4W2NvbHVtbl0sIHZhbHVlOiAnICcgfQoJZW5kCmVuZAoKIyBnYW1lIHBhcnQKbWFyayA9IFtdCmlmIHJhbmQoMS4uMikgPT0gMQoJbWFya1sxXSA9ICdYJwoJbWFya1syXSA9ICdPJwplbHNlCgltYXJrWzFdID0gJ08nCgltYXJrWzJdID0gJ1gnCmVuZAoKdHVybiA9IDEgIyAxc3QgcGxheWVyJ3MgdHVybgpiZWdpbgoJYmVnaW4KCQljaCA9IHAuZ2V0Y2gKCQlwLm1vdmVfY3Vyc29yKGNoLCBjdXJzb3JfcG9zKQoJZW5kIHVudGlsIGNoID09ID9ccwoJaWYgcC5wbGFjZV9tYXJrKG1hcmtbdHVybl0sIG1hcmtzLCBjdXJzb3JfcG9zKQoJCXJlc3VsdCA9IHAuY2hlY2tfbWFya3MobWFya3MsIG1hcmtbdHVybl0pCgkJdW5sZXNzIHJlc3VsdAoJCQloLnNldHBvcygwLCAwKQoJCQlpZiB0dXJuID09IDEKCQkJCXR1cm4gPSAyCgkJCQloLmFkZHN0ciAicGxheWVyICN7dHVybn0ncyB0dXJuIi5yanVzdCgzOCkKCQkJZWxzZQoJCQkJdHVybiA9IDEKCQkJCWguYWRkc3RyICJwbGF5ZXIgI3t0dXJufSdzIHR1cm4iLmxqdXN0KDM4KQoJCQllbmQKCQkJaC5yZWZyZXNoCgkJZW5kCgllbmQKZW5kIHVudGlsIHJlc3VsdAoKaC5zZXRwb3MoMCwgMCkKaWYgcmVzdWx0ID09ICJkcmF3IgoJaC5hZGRzdHIgIkRSQVchIi5jZW50ZXIoMzgpCmVsc2UKCWguYWRkc3RyICJQTEFZRVIgI3t0dXJufSBXSU5TISIuY2VudGVyKDM4KQplbmQKaC5zZXRwb3MoMiwgMCkKaC5hZGRzdHIgIlByZXNzIGFueSBrZXkgdG8gZXhpdC4iLmNlbnRlcigzOCkKaC5yZWZyZXNoCgpwLmdldGNoCnAuY2xvc2UKCmNsb3NlX3NjcmVlbgo=