login

<     >

2023-11-21 17:08:34 (UTC-03:00)

Marcel Rodrigues <marcelgmr@gmail.com>

first commit: tui version with weak bot

diff --git a/yacht.lua b/yacht.lua
new file mode 100644
index 0000000..731037d
--- /dev/null
+++ b/yacht.lua
@@ -0,0 +1,433 @@
+local arco = require "arco"
+
+local cats = {
+    "full-house", "four-of-a-kind", "little-straight", "big-straight",
+    "choice", "yacht", "1s", "2s", "3s", "4s", "5s", "6s"
+}
+
+local function calc_total(sheet)
+    local total = 0
+    for _, cat in ipairs(cats) do
+        total = total + (sheet[cat] or 0)
+    end
+    return total
+end
+
+local function calc_data(dice)
+    local sum = 0
+    local hist = {0, 0, 0, 0, 0, 0}
+    for _, die in ipairs(dice) do
+        sum = sum + die
+        hist[die] = hist[die] + 1
+    end
+    local min_count, max_count = math.huge, -math.huge
+    for i = 1, 6 do
+        local count = hist[i]
+        if count > 0 then
+            if count < min_count then
+                min_count = count
+            end
+            if count > max_count then
+                max_count = count
+            end
+        end
+    end
+    return sum, hist, min_count, max_count
+end
+
+local function calc_points(dice, cat)
+    local sum, hist, min_count, max_count = calc_data(dice)
+    local points = 0
+    if cat == "full-house" then
+        if min_count == 2 and max_count == 3 then
+            points = sum
+        end
+    elseif cat == "four-of-a-kind" then
+        if max_count >= 4 then
+            for i = 1, 6 do
+                if hist[i] >= 4 then
+                    points = 4 * i
+                    break
+                end
+            end
+        end
+    elseif cat == "little-straight" then
+        if max_count == 1 and hist[6] == 0 then
+            points = 30
+        end
+    elseif cat == "big-straight" then
+        if max_count == 1 and hist[1] == 0 then
+            points = 30
+        end
+    elseif cat == "choice" then
+        points = sum
+    elseif cat == "yacht" then
+        if max_count == 5 then
+            points = 50
+        end
+    else
+        local num = tonumber(cat:sub(1, 1))
+        points = num * hist[num]
+    end
+    return points
+end
+
+local Yacht = {}
+Yacht.__index = Yacht
+
+function Yacht:init()
+    self.term:setup()
+    self.term:enter_altbuf()
+    self.term:hide_cursor()
+end
+
+function Yacht:quit()
+    self.term:show_cursor()
+    self.term:exit_altbuf()
+    self.term:restore()
+end
+
+function Yacht:setup_game()
+    self.term:clear()
+    self.nh = tonumber(self.term:input("number of human players: "))
+    self.nc = tonumber(self.term:input("number of computer players: "))
+    self.np = self.nh + self.nc
+    self.players = {}
+    for i = 1, self.nh do
+        table.insert(self.players, "H-"..i)
+    end
+    for i = 1, self.nc do
+        table.insert(self.players, "C-"..i)
+    end
+    self.prng:shuffle(self.players)
+    self.sheets = {}
+    for i = 1, self.np do
+        table.insert(self.sheets, {})
+    end
+end
+
+function Yacht:run_game()
+    for i = 1, 12 do
+        self:run_round()
+    end
+    self:show_score()
+end
+
+function Yacht:run_round()
+    self.term:clear()
+    for i, p in ipairs(self.players) do
+        self.pi = i
+        self:show_sheet()
+        local sheet = self.sheets[i]
+        local is_bot = p:sub(1, 1) == "C"
+        local dice = self:do_rolls(is_bot)
+        local sel
+        if is_bot then
+            sel = self.bot:pick(sheet, dice)
+            self.term:delay(1)
+            for cur, cat in ipairs(cats) do
+                if sheet[cat] == nil then
+                    self:putsel(cur)
+                    self.term:delay(2)
+                    self:remsel(cur)
+                    if cur == sel then
+                        break
+                    end
+                end
+            end
+            self.term:delay(2)
+        else
+            sel = self:pick()
+        end
+        local cat = cats[sel]
+        local points = calc_points(dice, cat)
+        sheet[cat] = points
+        self:putpoints(sel, points)
+        self.term:goto(2+7*(i-1), 2)
+        io.write(("%3d"):format(calc_total(sheet)))
+        self.term:get_key()
+        self.term:goto(1, 1)
+    end
+end
+
+function Yacht:show_sheet()
+    local sheet = self.sheets[self.pi]
+    local fmt
+    for i, p in ipairs(self.players) do
+        if i == self.pi then
+            fmt = "[%s]  "
+        else
+            fmt = " %s   "
+        end
+        io.write(fmt:format(p))
+    end
+    self.term:goto(1, 2)
+    for i, p in ipairs(self.players) do
+        if i == self.pi then
+            fmt = "[%3d]  "
+        else
+            fmt = " %3d   "
+        end
+        io.write(fmt:format(calc_total(self.sheets[i])))
+    end
+    self.term:goto(1, 8)
+    for i = 1, #cats/2 do
+        local cat_l = cats[i]
+        local cat_r = cats[i+6]
+        local val_l = sheet[cat_l] or "--"
+        if val_l == 0 then
+            val_l = "XX"
+        end
+        local val_r = sheet[cat_r] or "--"
+        if val_r == 0 then
+            val_r = "XX"
+        end
+        local fmt = "%16s: %2s"..(" "):rep(4).."%8s: %2s"
+        print(fmt:format(cat_l, val_l, cat_r, val_r))
+    end
+    self.term:goto(1, 4)
+end
+
+function Yacht:show_score()
+    self.term:clear()
+    self.term:down(3)
+    print("== final score ==\n")
+    for i, p in ipairs(self.players) do
+        print(("%s: %d"):format(p, calc_total(self.sheets[i])))
+    end
+    print("\n=================")
+    self.term:get_key()
+end
+
+function Yacht:do_rolls(is_bot)
+    local pad = (" "):rep(15)
+    print(pad.."roll: 1")
+    self.term:clear_line()
+    self.term.down()
+    local dice, sel = {}, {}
+    io.write((" "):rep(12).."[ - - - - - ]")
+    self:roll(dice)
+    for i = 2, 3 do
+        if is_bot then
+            local sheet = self.sheets[self.pi]
+            sel = self.bot:select(sheet, dice, i-1)
+            self.term:delay(3)
+            self.term:goto_col(15)
+            for cur = 1, 5 do
+                io.write("^")
+                self.term:left()
+                self.term:delay(2)
+                self.term:up(2)
+                io.write(sel[cur] and "= " or "  ")
+                self.term:delay(1)
+                self.term:down(2)
+                self.term:clear_line()
+            end
+            self.term:delay(3)
+            self.term:up(3)
+            self.term:goto_col(1)
+        else
+            self:select(sel)
+        end
+        for j = 1, 5 do
+            if not sel[j] then
+                dice[j] = nil
+            end
+        end
+        print(pad.."roll: "..i.."\n")
+        self:roll(dice)
+    end
+    self.term:up(2)
+    self.term:clear_line()
+    return dice
+end
+
+function Yacht:roll(dice)
+    self.term:goto_col(15)
+    for i = 1, 5 do
+        if dice[i] == nil then
+            io.write("- ")
+        else
+            self.term:right(2)
+        end
+    end
+    self.term:goto_col(15)
+    for i = 1, 5 do
+        if dice[i] == nil then
+            dice[i] = self.prng:randint(1, 6)
+            self.term:delay(1)
+            io.write(("%d "):format(dice[i]))
+        else
+            self.term:right(2)
+        end
+    end
+    self.term:down()
+end
+
+function Yacht:select(sel)
+    local cur = 1
+    for i = 1, 5 do
+        if not sel[i] then
+            cur = i
+            break
+        end
+    end
+    self.term:goto_col(cur * 2 + 13)
+    io.write("^")
+    while true do
+        local key = self.term:get_key()
+        local update = true
+        if key == "\n" then
+            break
+        elseif key == "a" or key == "h" then
+            cur = (cur > 1) and (cur - 1) or 5
+        elseif key == "d" or key == "l" then
+            cur = (cur < 5) and (cur + 1) or 1
+        elseif key == " " then
+            sel[cur] = not sel[cur]
+        else
+            update = false
+        end
+        if update then
+            self.term:up(2)
+            self.term:clear_line()
+            self.term:goto_col(15)
+            for i = 1, 5 do
+                io.write(sel[i] and "= " or "  ")
+            end
+            self.term:down(2)
+            self.term:clear_line()
+            self.term:goto_col(cur * 2 + 13)
+            io.write("^")
+        end
+    end
+    self.term:clear_line()
+    self.term:up(3)
+    self.term:goto_col(1)
+end
+
+function Yacht:getpos(s)
+    if s <= #cats/2 then
+        return 18, s+7
+    else
+        return 34, s+1
+    end
+end
+
+function Yacht:putsel(s)
+    self.term:goto(self:getpos(s))
+    io.write("[")
+    self.term:right(2)
+    io.write("]")
+end
+
+function Yacht:remsel(s)
+    self.term:goto(self:getpos(s))
+    io.write(" -- ")
+end
+
+function Yacht:putpoints(s, p)
+    self.term:goto(self:getpos(s))
+    if p == 0 then
+        io.write(" XX ")
+    else
+        io.write((" %2d "):format(p))
+    end
+end
+
+function Yacht:pick()
+    local sheet = self.sheets[self.pi]
+    local sel
+    for i, cat in ipairs(cats) do
+        if sheet[cat] == nil then
+            sel = i
+            break
+        end
+    end
+    self:putsel(sel)
+    while true do
+        local key = self.term:get_key()
+        local update = true
+        local newsel = sel
+        if key == "\n" then
+            break
+        elseif key == "w" or key == "k" then
+            repeat
+                newsel = (newsel > 1) and newsel - 1 or #cats
+            until sheet[cats[newsel]] == nil
+        elseif key == "s" or key == "j" then
+            repeat
+                newsel = (newsel < #cats) and newsel + 1 or 1
+            until sheet[cats[newsel]] == nil
+        elseif key == "a" or key == "d" or key == "h" or key == "l" then
+            newsel = (sel <= #cats/2) and sel + 6 or sel - 6
+            if sheet[cats[newsel]] ~= nil then
+                update = false
+            end
+        else
+            update = false
+        end
+        if update then
+            self:remsel(sel)
+            sel = newsel
+            self:putsel(sel)
+        end
+    end
+    return sel
+end
+
+local function new_yacht(Bot)
+    local term = arco.tui.new_term()
+    local prng = arco.rand.new_prng()
+    local bot = setmetatable({prng=prng}, Bot)
+    return setmetatable({term=term, prng=prng, bot=bot}, Yacht)
+end
+
+local Bob = {}
+Bob.__index = Bob
+
+function Bob:select(sheet, dice, roll)
+    local sum, hist, min_count, max_count = calc_data(dice)
+    local sel = {}
+    if max_count > 1 then
+        for i = 1, 5 do
+            sel[i] = hist[dice[i]] == max_count
+        end
+    end
+    return sel
+end
+
+function Bob:pick(sheet, dice, roll)
+    local min_points, max_points = 99, 0
+    local sel, sel_min, sel_max
+    for cur, cat in ipairs(cats) do
+        if sheet[cat] == nil then
+            local points = calc_points(dice, cat)
+            if points > max_points then
+                sel_max = cur
+                max_points = points
+            end
+            if points > 0 and points < min_points then
+                sel_min = cur
+                min_points = points
+            end
+        end
+    end
+    if max_points > 20 or (sel_max ~= nil and sel_max > 6) then
+        sel = sel_max
+    else
+        sel = sel_min
+    end
+    if sel == nil then
+        repeat
+            sel = self.prng:randint(1, #cats)
+        until sheet[cats[sel]] == nil
+    end
+    return sel
+end
+
+local yacht = new_yacht(Bob)
+yacht:init()
+yacht:setup_game()
+yacht:run_game()
+yacht:quit()