mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-04-19 13:35:47 +00:00
The purpose is mainly to exhibit certain limitations that come with such less common programming models, to show users how to program interactive tools in Lua, and how to connect interactively. Other use cases that could be envisioned are "top" and various monitoring utilities, with sliding graphs etc. Lua is particularly attractive for this usage, easy to program, well known from most AI tools (including its integration into haproxy), making such programs very quick to obtain in their basic form, and to improve later. A very limited example game is provided, following the principle of a very popular one, where the player must compose lines from falling pieces. It quickly revealed the need to the ability to enforce a timeout to applet:receive(). Other identified limitations include the difficulty from the Lua side to monitor multiple events at once, but it seems that callbacks and/or event dispatchers would be useful here. At the moment the CLI is not workable (it interactivity was broken in 2.9 when line buffering was adopted), though it was verified that it works with older releases. The command needed to connect to the game is displayed as a notice message during boot.
251 lines
8.2 KiB
Lua
251 lines
8.2 KiB
Lua
-- 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)
|