?-
  G_StartLevel := 2,
  G_MaxLevel := 10,
  G_Level := G_StartLevel,
  G_Points := 0,
  G_Time := 0,
  G_GameOver := 0,
  G_Selected := 0,
  array(table,200,0), 
  array(left,G_MaxLevel + 1, 10), 
  array(right,G_MaxLevel + 1, 0),
  array(color,G_MaxLevel + 1, 0),
  array(top, 4*G_MaxLevel + 4, 0),
  array(bottom, 4 * G_MaxLevel + 4, 0),
  array(speed,G_MaxLevel + 1, 0),
  window( _, _, win_func(_), "MuliTetris, Level "+G_Level+", 0 points", 100, 100, 300, 540).

menu_new_game(press):-
  G_Level := G_StartLevel,
  G_Points := 0,
  G_Time := 0,
  G_GameOver := 0,
  G_Selected := 0,
  set_text( "MuliTetris, Level "+G_Level+", "+G_Points+" points", _),
  clean_fig,
  clean_table,
  draw_table.

menu_help(press):-
  message("Muli Tetris - Help",
  "Use the arrows to move the selected figure and the Space bar in order to change the selection.",i).

win_func(init):-
  menu(normal, _, _, menu_new_game(_), "&New Game"),
  menu(right, _, _, menu_help(_), "&Help"),
  T is set_timer(_, 0.1, time_func).

win_func(paint):-
  pen(3, rgb(255,0,0)),
  line(48,48,251,48,251,451,48,451,48,48),
  pen(0,0),
  draw_table,
  draw_all_fig.

win_func(key_down(40, Rep)):- % down
  speed(G_Selected) := 19.

win_func(key_down(37, Rep)):- % left
  shift(left, G_Selected).

win_func(key_down(39, Rep)):- % right
  shift(right, G_Selected).

win_func(key_down(32, Rep)):- % space
  (G_Selected =:= 0 ->
    bottom_most(G_Selected, 0),
    (G_Selected>0 -> draw_fig(G_Selected, 1))
  else
    Next := 0,
    find_next(Next, G_Selected),
    (Next =:= 0 ->
      bottom_most(Next, 0)
    ),
    (Next>0 ->
      Old := G_Selected,
      G_Selected := Next,
      draw_fig(Old, 1),
      draw_fig(Next, 1)
    )
  ).

win_func(key_down(38, Rep)):- % up
  G_Selected > 0,
  roll(G_Selected),
  not(bottom_collision),
  not(collision(G_Selected, _)),
  move(G_Selected).

time_func(end):-
  G_GameOver = 0,
  G_Time := G_Time+1,
  (G_Time mod 1000 =:= 0, G_Level<G_MaxLevel->
    G_Level := G_Level+1,
    set_text( "MuliTetris, Level "+G_Level+", "+G_Points+" points", _)
  ),
  Line:=20, End:=0,
  find_full_line(Line, End),
  (Line>End->
    go_down(Line, End, 1),
    go_down_fig(Line, End, 1),
    draw_table,
    G_Points:=G_Points+1,
    set_text( "MuliTetris, Level "+G_Level+", "+G_Points+" points", _),
    beep
  ),
  (G_Selected =:= 0 ->
    bottom_most(G_Selected, 0)
  ),
  for(F, 1, G_Level),
    falling(F, speed(F)),
    (collision(F, Who) ->
      Speed := speed(Who),
      speed(Who) := speed(F),
      speed(F) := Speed
    else (bottom_collision ->
      (F =:= G_Selected ->
        G_Selected := 0
      ),
      lay_down(F),
      fill_table(F),
      move(F),
      left(F) := 10
    else
      move(F)
    )),
    fail.

time_func(end):-
  G_GameOver = 0,
  F is random(G_Level)+1,
  left(F)=:=10,
  create(F),
  not(collision(F, _)),
  (bottom_collision->
    G_GameOver := 1,
    message("Game Over", "Your result is "+ G_Points, i)
  else
    jump_in(0, F)
  ).

draw_table:-
  for(Y,0,19),
    for(X,0,9),
      draw_sqr(X,Y),
      fail.
draw_table.

clean_table:-
  for(Y,0,19),
    for(X,0,9),
      table(10*Y+X):=0,
      fail.
clean_table.

clean_fig:-
    for(F, 1, G_MaxLevel),
      left(F) := 10,
      fail.
clean_fig.

draw_sqr(X,Y):-
  S is table(10*Y+X),
  color(S,Ex),
  brush(Ex),
  rect(50+20*X,50+20*Y,71+20*X,71+20*Y).

draw_all_fig:-
    for(F, 1, G_Level),
      left(F) < 10,
      draw_fig(F, 1),
      fail.
draw_all_fig.

draw_fig(F, Flag):-
  (Flag = 1 ->
    C := color(F)
  else
    C := 0
  ),
  (F =:= G_Selected, Flag = 1 ->
    C := C + 7
  ),
  color(C, Ex),
  brush(Ex),
  for(R, left(F), right(F)),
    rect(50+20*R, 50+top(4*F+R-left(F)),
               71+20*R, 51+bottom(4*F+R-left(F))),
  fail.
draw_fig(F, Flag).

falling(F, Speed):-
  left(0) := left(F),
  right(0) := right(F),
  for(I, 0, 3),
    top(I) := top(4*F+I) + Speed,
    bottom(I) := bottom(4*F+I) + Speed,
    fail.
falling(F, Speed).

jump_in(From, To):-
  left(To) := left(From),
  right(To) := right(From),
  for(R, 0, 3),
    top(4*To+R) := top(4 * From + R),
    bottom(4*To+R) := bottom(4 * From + R),
    fail.
jump_in(From, To).

move(F):-
  draw_fig(F, 0),
  jump_in(0, F),
  draw_fig(F, 1).

create(F):-
  color(F) := random(7)+1,
  speed(F) := 1 + random(G_Level-G_MaxLevel+19),
  Shape is color(F), % random(7)+1
  shape(Shape).

collision(Old, Who):-
  for(R, left(0), right(0)),
    for(Who, 1, G_Level),
      Who =\= Old,
      left(Who) =\= 10,
      R >= left(Who),
      R =< right(Who),
      intersect(top(R-left(0)), bottom(R - left(0)),
                top(4*Who+R-left(Who)), bottom(4*Who+R-left(Who))).

intersect(A1, B1, A2, B2):-
  A2 =< A1, A1 =< B2; %; = OR
  A1 =< A2, A2 =< B1.

bottom_most(F, Height):-
  for(I, 1, G_Level),
    left(I)<10,
    for(R, left(I), right(I)),
      A is bottom(4*I + R-left(I)),
      A > Height,
      Height := A,
      F := I,
      fail.
bottom_most(F, Height).

bottom_collision:-
  left(0)<0; right(0)>9.

bottom_collision:-
  Top := 10000,
  calc_top(Top, 0),
  Top<0.

bottom_collision:-
  for(R, left(0), right(0)),
    bottom(R-left(0)) > 400.

bottom_collision:-
  for(R, left(0), right(0)),
    Y1 is top(R-left(0)) // 20,
    Y2 is bottom(R-left(0)) // 20,
    for(I, Y1, Y2 - true_value(top(0) mod 20 =:= 0)),    
      table(10*I + R) > 0.

lay_down(F):-
  for(R, left(F), right(F)),
    Y1 is (top(4*F+R-left(F))+19) // 20,
    Y2 is (bottom(4*F+R-left(F))+19) // 20,
    top(R-left(F)) := Y1 * 20,
    bottom(R-left(F)) := Y2 * 20,
    fail.
lay_down(F).

fill_table(F):-
  for(R, left(F), right(F)),
    Y1 is (top(4*F+R-left(F))+19) // 20,
    Y2 is (bottom(4*F+R-left(F))+19) // 20,
    for(I, Y1, Y2-1),
      table(10*I+R) := color(F),
      fail.
fill_table(F).

shift(Direction, F):-
  F > 0,
  jump_in(F, 0),
  (Direction = left ->
    left(0) := left(F) - 1,
    right(0) := right(F) - 1
  else
    left(0) := left(F) + 1,
    right(0) := right(F) + 1
  ),
  (bottom_collision->
    More is 20 - top(0) mod 20,
    More > 0,
    More < 2*speed(F),
    lay_down(0),
    not(bottom_collision),
    not(collision(F, _)),
    move(F)
  else (collision(F, Who) ->
    shift(Direction, Who)
  else
    move(F)
  )).

calc_bottom(Res, F) :-
  for(R, left(F), right(F)),
    S is bottom(4*F+R-left(F)),
    Res < S,
    Res := S,
    fail.
calc_bottom(Res, F).
    
calc_top(Res, F) :-
  for(R, left(F), right(F)),
    S is top(4*F+R-left(F)),
    Res > S,
    Res := S,
    fail.
calc_top(Res, F).

before(F1, Bottom1, F2, Bottom2) :-
  Bottom1 < Bottom2; Bottom1 = Bottom2, F1 < F2.

find_next(Next, Fig) :-
  Bottom1 := 0,
  calc_bottom(Bottom1, Fig),
  BestBottom := 0,
  for(F, 1, G_Level),
    left(F)<10,
    Bottom2 := 0,
    calc_bottom(Bottom2, F),
    before(F, Bottom2, Fig, Bottom1),
    before(Next, BestBottom, F, Bottom2),
    Next := F,
    BestBottom := Bottom2,
    fail.
find_next(Next, Fig).

roll(F):-
  Top := 10000,
  calc_top(Top, F),
  Bottom := 0,
  calc_bottom(Bottom, F),
  X is right(F) - left(F) + 1,
  Y is (Bottom - Top) // 20,
  (Y = 1 ->
    left(0) := left(F) + 1,
    right(0) := left(0),
    top(0) := Top - 30,
    bottom(0) := Bottom + 30
  else (X = 1 ->
    left(0) := left(F) - 1,
    right(0) := left(0) + 3,
    top(0) := Top + 30,
    top(1) := top(0),
    top(2) := top(0),
    top(3) := top(0),
    bottom(0) := Bottom - 30,
    bottom(1) := bottom(0),
    bottom(2) := bottom(0),
    bottom(3) := bottom(0)
  else ( X = 2, Y = 2 ->
    fail
  else
    left(0) := left(F),
    (X = 2 ->
      Bottom2 := Bottom-10,
      right(0) := left(0) + 2
    else
      Bottom2 := Bottom+10,
      right(0) := left(0) + 1
    ),
    make_rows(F, Top, Bottom2)
  ))).

make_rows(F, Top, Bottom2):-
  for(R, left(0), right(0)),
    make_row(F, Top, Bottom2, R),
    fail.
make_rows(F, Top, Bottom2).

make_row(F, Top, Bottom2, R):-
  Row is R - left(0),
  Level is Top + 20*Row + 10,
  calc_start(F, Level, Start),
  bottom(Row) := Bottom2 - 20*Start,
  calc_end(F, Level, Start),
  top(Row) := Bottom2 - 20*Start.

calc_start(F, Level, Start) :-
  (top(4*F) < Level, Level < bottom(4*F) ->
    Start := 0
  else (top(4*F+1) < Level, Level < bottom(4*F+1) ->
    Start := 1
  else
    Start := 2
  )).

calc_end(F, Level, Start) :-
  (left(F)+Start+1>right(F); top(4*F+Start+1) >= Level; Level >= bottom(4*F+Start+1) ->
    Start := Start+1
  else (left(F)+Start+2>right(F); top(4*F+Start+2) >= Level; Level >= bottom(4*F+Start+2) ->
    Start := Start+2
  else
    Start := Start+3
  )).

find_full_line(Line, End):-
  repeat,
  Line:=Line-1,
  not(has_hole(Line), Line>End), !.

has_hole(Line):-
  for(X, 0, 9),
    table(10*Line+X)=:=0.

go_down(Start, End, Shift):-
  for(I, Start, End, -1),
    copy_line(I, Shift),
    fail.
go_down(Start, End, Shift).

copy_line(Line, Shift):-
  for(X, 0, 9),
    table(10*Line+X):=table(10*(Line-Shift)+X),
    fail.
copy_line(Line, Shift).

go_down_fig(Start, End, Shift):-
  for(F, 1, G_Level),
    left(F)<10,
    Bottom := 0,
    calc_bottom(Bottom, F),
    Bottom<20*Start,
    Bottom>20*End,
    falling(F, 20*Shift),
    jump_in(0, F),
    fail.
go_down_fig(Start, End, Shift).

shape(1):-
  left(0):= random(7),
  right(0):= left(0)+3,
  top(0) := 20,
  top(1) := 20,
  top(2) := 20,
  top(3) := 20,
  bottom(0) := 40,
  bottom(1) := 40,
  bottom(2) := 40,
  bottom(3) := 40.

shape(2):-
  left(0):= random(8),
  right(0):= left(0)+2,
  top(0) := 20,
  top(1) := 0,
  top(2) := 20,
  bottom(0) := 40,
  bottom(1) := 40,
  bottom(2) := 40.

shape(3):-
  left(0):= random(8),
  right(0):= left(0)+2,
  top(0) := 0,
  top(1) := 20,
  top(2) := 20,
  bottom(0) := 40,
  bottom(1) := 40,
  bottom(2) := 40.

shape(4):-
  left(0):= random(8),
  right(0):= left(0)+2,
  top(0) := 20,
  top(1) := 20,
  top(2) := 0,
  bottom(0) := 40,
  bottom(1) := 40,
  bottom(2) := 40.

shape(5):-
  left(0):= random(8),
  right(0):= left(0)+2,
  top(0) := 0,
  top(1) := 0,
  top(2) := 20,
  bottom(0) := 20,
  bottom(1) := 40,
  bottom(2) := 40.

shape(6):-
  left(0):= random(8),
  right(0):= left(0)+2,
  top(0) := 20,
  top(1) := 0,
  top(2) := 0,
  bottom(0) := 40,
  bottom(1) := 40,
  bottom(2) := 20.

shape(7):-
  left(0):= random(9),
  right(0):= left(0)+1,
  top(0) := 0,
  top(1) := 0,
  bottom(0) := 40,
  bottom(1) := 40.

color(0, system_color(window)).
color(1, rgb(200,0,0)).
color(2, rgb(0,200,0)).
color(3, rgb(0,0,200)).
color(4, rgb(200,200,0)).
color(5, rgb(200,0,200)).
color(6, rgb(0,200,200)).
color(7, rgb(0,100,0)).
color(8, rgb(255,100,100)).
color(9, rgb(100,255,100)).
color(10, rgb(100,100,255)).
color(11, rgb(255,255,100)).
color(12, rgb(255,100,255)).
color(13, rgb(100,255,255)).
color(14, rgb(100,150,100)).
    

