--- /dev/null
+-- Example game of falling pieces for HAProxy CLI/Applet
+local board_width = 10
+local board_height = 20
+local game_name = "Lua Tris Demo"
+
+-- Shapes with IDs for color mapping
+local pieces = {
+ {id = 1, shape = {{1,1,1,1}}}, -- I (Cyan)
+ {id = 2, shape = {{1,1},{1,1}}}, -- O (Yellow)
+ {id = 3, shape = {{0,1,0},{1,1,1}}}, -- T (Purple)
+ {id = 4, shape = {{0,1,1},{1,1,0}}}, -- S (Green)
+ {id = 5, shape = {{1,1,0},{0,1,1}}}, -- Z (Red)
+ {id = 6, shape = {{1,0,0},{1,1,1}}}, -- J (Blue)
+ {id = 7, shape = {{0,0,1},{1,1,1}}} -- L (Orange)
+}
+
+-- ANSI escape codes
+local clear_screen = "\27[2J"
+local cursor_home = "\27[H"
+local cursor_hide = "\27[?25l"
+local cursor_show = "\27[?25h"
+local reset_color = "\27[0m"
+
+local color_codes = {
+ [1] = "\27[1;36m", -- I: Cyan
+ [2] = "\27[1;37m", -- O: White
+ [3] = "\27[1;35m", -- T: Purple
+ [4] = "\27[1;32m", -- S: Green
+ [5] = "\27[1;31m", -- Z: Red
+ [6] = "\27[1;34m", -- J: Blue
+ [7] = "\27[1;33m" -- L: Yellow
+}
+
+local function init_board()
+ local board = {}
+ for y = 1, board_height do
+ board[y] = {}
+ for x = 1, board_width do
+ board[y][x] = 0 -- 0 for empty, piece ID for placed blocks
+ end
+ end
+ return board
+end
+
+local function can_place_piece(board, piece, px, py)
+ for y = 1, #piece do
+ for x = 1, #piece[1] do
+ if piece[y][x] == 1 then
+ local board_x = px + x - 1
+ local board_y = py + y - 1
+ if board_x < 1 or board_x > board_width or board_y > board_height or
+ (board_y >= 1 and board[board_y][board_x] ~= 0) then
+ return false
+ end
+ end
+ end
+ end
+ return true
+end
+
+local function place_piece(board, piece, piece_id, px, py)
+ for y = 1, #piece do
+ for x = 1, #piece[1] do
+ if piece[y][x] == 1 then
+ local board_x = px + x - 1
+ local board_y = py + y - 1
+ if board_y >= 1 and board_y <= board_height then
+ board[board_y][board_x] = piece_id -- Store piece ID for color
+ end
+ end
+ end
+ end
+end
+
+local function clear_lines(board)
+ local lines_cleared = 0
+ local y = board_height
+ while y >= 1 do
+ local full = true
+ for x = 1, board_width do
+ if board[y][x] == 0 then
+ full = false
+ break
+ end
+ end
+ if full then
+ table.remove(board, y)
+ table.insert(board, 1, {})
+ for x = 1, board_width do
+ board[1][x] = 0
+ end
+ lines_cleared = lines_cleared + 1
+ else
+ y = y - 1
+ end
+ end
+ return lines_cleared
+end
+
+local function rotate_piece(piece, piece_id, px, py, board)
+ local new_piece = {}
+ for x = 1, #piece[1] do
+ new_piece[x] = {}
+ for y = 1, #piece do
+ new_piece[x][#piece + 1 - y] = piece[y][x]
+ end
+ end
+ if can_place_piece(board, new_piece, px, py) then
+ return new_piece
+ end
+ return piece
+end
+
+function render(applet, board, piece, piece_id, px, py, score)
+ local output = clear_screen .. cursor_home
+ output = output .. game_name .. " - Lines: " .. score .. "\r\n"
+ output = output .. "+" .. string.rep("-", board_width * 2) .. "+\r\n"
+ for y = 1, board_height do
+ output = output .. "|"
+ for x = 1, board_width do
+ local char = " "
+ -- Current piece
+ for py_idx = 1, #piece do
+ for px_idx = 1, #piece[1] do
+ if piece[py_idx][px_idx] == 1 then
+ local board_x = px + px_idx - 1
+ local board_y = py + py_idx - 1
+ if board_x == x and board_y == y then
+ char = color_codes[piece_id] .. "[]" .. reset_color
+ end
+ end
+ end
+ end
+ -- Placed blocks
+ if board[y][x] ~= 0 then
+ char = color_codes[board[y][x]] .. "[]" .. reset_color
+ end
+ output = output .. char
+ end
+ output = output .. "|\r\n"
+ end
+ output = output .. "+" .. string.rep("-", board_width * 2) .. "+\r\n"
+ output = output .. "Use arrow keys to move, Up to rotate, q to quit"
+ applet:send(output)
+end
+
+function handler(applet)
+ local board = init_board()
+ local piece_idx = math.random(#pieces)
+ local current_piece = pieces[piece_idx].shape
+ local piece_id = pieces[piece_idx].id
+ local piece_x = math.floor(board_width / 2) - math.floor(#current_piece[1] / 2)
+ local piece_y = 1
+ local score = 0
+ local game_over = false
+ local delay = 500
+
+ if not can_place_piece(board, current_piece, piece_x, piece_y) then
+ game_over = true
+ end
+
+ applet:send(cursor_hide)
+
+ -- fall the piece by one line every delay
+ local function fall_piece()
+ while not game_over do
+ piece_y = piece_y + 1
+ if not can_place_piece(board, current_piece, piece_x, piece_y) then
+ piece_y = piece_y - 1
+ place_piece(board, current_piece, piece_id, piece_x, piece_y)
+ score = score + clear_lines(board)
+ piece_idx = math.random(#pieces)
+ current_piece = pieces[piece_idx].shape
+ piece_id = pieces[piece_idx].id
+ piece_x = math.floor(board_width / 2) - math.floor(#current_piece[1] / 2)
+ piece_y = 1
+ if not can_place_piece(board, current_piece, piece_x, piece_y) then
+ game_over = true
+ end
+ end
+ core.msleep(delay)
+ end
+ end
+
+ core.register_task(fall_piece)
+
+ local function drop_piece()
+ while can_place_piece(board, current_piece, piece_x, piece_y) do
+ piece_y = piece_y + 1
+ end
+ piece_y = piece_y - 1
+ place_piece(board, current_piece, piece_id, piece_x, piece_y)
+ score = score + clear_lines(board)
+ piece_idx = math.random(#pieces)
+ current_piece = pieces[piece_idx].shape
+ piece_id = pieces[piece_idx].id
+ piece_x = math.floor(board_width / 2) - math.floor(#current_piece[1] / 2)
+ piece_y = 1
+ if not can_place_piece(board, current_piece, piece_x, piece_y) then
+ game_over = true
+ end
+ render(applet, board, current_piece, piece_id, piece_x, piece_y, score)
+ end
+
+ while not game_over do
+ render(applet, board, current_piece, piece_id, piece_x, piece_y, score)
+
+ -- update the delay based on the score: 500 for 0 lines to 100ms for 100 lines.
+ if score >= 100 then
+ delay = 100
+ else
+ delay = 500 - 4*score
+ end
+
+ local input = applet:receive(1, delay)
+ if input then
+ if input == "q" then
+ game_over = true
+ elseif input == "\27" then
+ local a = applet:receive(1, delay)
+ if a == "[" then
+ local b = applet:receive(1, delay)
+ if b == "A" then -- Up arrow (rotate clockwise)
+ current_piece = rotate_piece(current_piece, piece_id, piece_x, piece_y, board)
+ elseif b == "B" then -- Down arrow (full drop)
+ drop_piece()
+ elseif b == "C" then -- Right arrow
+ piece_x = piece_x + 1
+ if not can_place_piece(board, current_piece, piece_x, piece_y) then
+ piece_x = piece_x - 1
+ end
+ elseif b == "D" then -- Left arrow
+ piece_x = piece_x - 1
+ if not can_place_piece(board, current_piece, piece_x, piece_y) then
+ piece_x = piece_x + 1
+ end
+ end
+ end
+ end
+ end
+ end
+
+ applet:send(clear_screen .. cursor_home .. "Game Over! Lines: " .. score .. "\r\n" .. cursor_show)
+end
+
+-- works as a TCP applet
+core.register_service("trisdemo", "tcp", handler)
+
+-- may also work on the CLI but requires an unbuffered handler
+core.register_cli({"trisdemo"}, "Play a simple falling pieces game", handler)