:- use_module(library(qml)). :- use_module(library(clpBNR)). %% A declarative, QML-like UI built on top of the qml module. %% %% - main/0 : a column with two images, a rectangle and a button whose %% `pressed` state is maintained reactively by qml_button/1. %% - pong/0 : a playable Pong whose per-frame logic is the pong/6 %% OnFrame phrase. main :- Ui = window{ title: "Helene", w: 400, h: 600, childs: column{ x: 0, y: 0, childs: [ image{ source: "examples/DSC03094.JPG", x: 0, y: Img1Y, w: 100, h: 100 }, image{ source: "examples/DSC03094.JPG", x: 0, y: Img2Y, w: 100, h: 100 }, rectangle{ color: rgba(200, 0, 0, 255), x: 0, y: RectY, fill: true, w: 100, h: 100 }, button{ color: rgba(0, 200, 0, 255), x: 0, y: BY, w: 100, h: 50, pressed: Pressed } ] } }, nth1(4, Ui.childs.childs, Button), State0 = state{ x: 0, y: 0, img1y: Img1Y, img2y: Img2Y, recty: RectY, by: BY, pressed: Pressed }, Save0 = state{pressed: false}, qml_run(Ui, qml_button(Button), State0, Save0). %%! \section{Pong} %% %% pong/6 is the OnFrame phrase: it reads the previous frame's paddle / %% ball positions from the saved state and binds the new ones, reacting to %% the mouse (left paddle) and the keyboard (right paddle). pong(P1Y, P2Y, X, Y, DX, DY) --> p1(P1Y), p2(P2Y), ball(X, Y, DX, DY, P1Y, P2Y). p1(P1Y) --> qml_get_save(p1y, P1Y0), qml_get_save(y, Y0), { ( { P1Y0 + 50 =< Y0 } -> { P1Y == P1Y0 + 10 } ; { P1Y0 >= Y0 + 10 } -> { P1Y == P1Y0 - 10 } ; P1Y = P1Y0 ) }. p2(P2Y) --> qml_get_save(p2y, P2Y0), ( qml_event(keyboard, keydown, Event) -> { keysym(Scancode, _, _) = Event.keysym, ( Scancode = 82 -> { P2Y1 == P2Y0 - 10 } ; Scancode = 81 -> { P2Y1 == P2Y0 + 10 } ; P2Y1 = P2Y0 ) } ; { P2Y1 = P2Y0 } ), { ( { P2Y1 =< 0 } -> P2Y = 0 ; { P2Y1 + 50 >= 400 } -> { P2Y == 350 } ; P2Y = P2Y1 ) }. ball(X, Y, DX, DY, P1Y, P2Y) --> qml_get_save(x, X0), qml_get_save(y, Y0), qml_get_save(dx, DX0), qml_get_save(dy, DY0), { { X1 == X0 + DX0, Y1 == Y0 + DY0 }, ( { P1Y =< Y1 + 10, Y1 =< P1Y + 50, X1 =< 10 } -> { X2 == 10 + (10 - X1), DX1 == -DX0 * 1.1, Offset == ((Y1 + 5) - (P1Y + 25) / 25), DY1 == DY0 * 1.1 } ; { P2Y =< Y1 + 10, Y1 =< P2Y + 50, X1 + 10 >= 590 } -> { X2 == 580 - (X1 + 10 - 590), DX1 == -DX0 * 1.1, Offset == ((Y1 + 5) - (P2Y + 25) / 25), DY1 == DY0 * 1.1 } ; X2 = X1, DX1 = DX0, DY1 = DY0 ), ( { X2 =< 0 } -> { X3 == -X2, DX2 == -DX1 } ; { X2 + 10 >= 600 } -> { X3 == 600 - (X2 + 10 - 600), DX2 == -DX1 } ; { X3 == X2 }, DX2 = DX1 ), ( { Y1 =< 0 } -> { Y2 == -Y1, DY2 == -DY1 } ; { Y1 + 10 >= 400 } -> { Y2 == 390 - (Y1 + 10 - 400), DY2 == -DY1 } ; { Y2 == Y1 }, DY2 = DY1 ), midpoint(X3, X4), midpoint(Y2, Y3), midpoint(DX2, DX), midpoint(DY2, DY), X is round(X4), Y is round(Y3) }. pong :- Ui = window{ title: "Pong", w: 600, h: 400, childs: [ rectangle{ id: p1, color: rgba(255, 255, 255, 255), fill: true, x: 0, y: P1Y, w: 10, h: 50 }, rectangle{ id: p2, color: rgba(255, 255, 255, 255), fill: true, x: 590, y: P2Y, w: 10, h: 50 }, rectangle{ id: ball, color: rgba(255, 255, 255, 255), fill: true, x: X, y: Y, w: 10, h: 10 } ] }, State0 = state{ p1y: P1Y, p2y: P2Y, x: X, y: Y, dx: DX, dy: DY }, DX0 is (random_float * 2 - 1) * 1, DY0 is (random_float * 2 - 1) * 1, Save0 = state{ p1y: 150, p2y: 150, x: 295, y: 195, dx: DX0, dy: DY0 }, qml_run(Ui, pong(P1Y, P2Y, X, Y, DX, DY), State0, Save0).