Number triplets game

From Rosetta Code
Revision as of 13:24, 10 May 2022 by Petelomax (talk | contribs) (→‎{{header|Phix}}: update, [eg] cdCanvasTextAlignment() has now become cdCanvasSetTextAlignment(), fixed online link)
Number triplets game is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
Task


The rules are simple:
You can move squares to yellow and blue ones, you can not move to black and white ones.
The goal is:
Number Triplets Game - goal image
To see how it works watch the next video:
Number Triplets Game in Ring - video
Please if you can attach a video file how your sample works.

Julia

Gtk library version. Click to choose a tile, then click move the tile in a single direction. <lang julia>using Random, Gtk

blue = GtkCssProvider(data="#blue {background:blue;}") black = GtkCssProvider(data="#black {background:black;}") yellow = GtkCssProvider(data="#yellow {background:yellow;}") white = GtkCssProvider(data="#white {background:white;}") red = GtkCssProvider(data="#red {background:red; font-size:40px}") spblue = GtkStyleProvider(blue) spblack = GtkStyleProvider(black) spyellow = GtkStyleProvider(yellow) spwhite = GtkStyleProvider(white) spred = GtkStyleProvider(red) sp = [spblue spblack spblack spblack spblack;

     spblue spyellow spred spred spred;
     spblue spwhite spwhite spwhite spwhite;
     spblue spyellow spred spred spred;
     spblue spwhite spwhite spwhite spwhite;
     spblue spyellow spred spred spred;
     spblue spwhite spwhite spwhite spwhite;
     spblue spyellow spred spred spred;
     spblue spblack spblack spblack spblack;]

colors = ["blue" "black" "black" "black" "black";

         "blue" "yellow" "red" "red" "red";
         "blue" "white" "white" "white" "white";
         "blue" "yellow" "red" "red" "red";
         "blue" "white" "white" "white" "white";
         "blue" "yellow" "red" "red" "red";
         "blue" "white" "white" "white" "white";
         "blue" "yellow" "red" "red" "red";
         "blue" "black" "black" "black" "black";]

startingpositions = [(2, 5), (2, 4), (2, 3), (2, 2), (4, 5), (4, 4), (4, 3), (4, 2),

                    (6, 5), (6, 4), (6, 3), (6, 2), (8, 5), (8, 4), (8, 3), (8, 2)]

labels = ["" "" "" "" "";

            "" "" "1" "1" "1";
            "" "" "" "" "";
            "" "" "2" "2" "2";
            "" "" "" "" "";
            "" "" "3" "3" "3";
            "" "" "" "" "";
            "" "" "4" "4" "4";
            "" "" "" "" ""]

mutable struct GameTile

   style::GtkStyleProvider
   color::String
   label::String

end

function NumberTripletsApp(w=800, h=500)

   moves, won, ygrid, xgrid, basetiles, tiles = 0, false, 5, 9, Matrix{GameTile}, Matrix{GameTile}
   buttons = [GtkButton() for i in 1:xgrid, j in 1:ygrid]
   for i in 1:xgrid, j in 1:ygrid
       set_gtk_property!(buttons[i, j], :expand, true)
   end
   function newgame()
       moves, won = 0, false
       basetiles = [GameTile(sp[i, j], colors[i, j], labels[i, j])
           for i in 1:xgrid, j in 1:ygrid]
       tiles = deepcopy(basetiles)
       for (i, p) in enumerate(shuffle(startingpositions))
           x, y = startingpositions[i]
           tiles[first(p), last(p)], tiles[x, y] = tiles[x, y], tiles[first(p), last(p)]
       end
       for i in 2:2:8, j in 2:5  # previous red tile start becomes yellow if empty
           basetiles[i, j] = GameTile(spyellow, "yellow", "")
       end
       for i in 1:xgrid, j in 1:ygrid
           tiles[i, j].color != "red" && (tiles[i, j] = basetiles[i, j])
       end
       setbuttonview()
   end
   function setbuttonview()
       # set text, color of each GtkButton in buttons as per corresponding tiles
       for i in 1:xgrid, j in 1:ygrid
           GAccessor.label(buttons[i, j], "  ")
           GAccessor.label(buttons[i, j], tiles[i, j].label)
           sc = GAccessor.style_context(buttons[i, j])
           push!(sc, tiles[i, j].style, 550)
           set_gtk_property!(buttons[i, j], :name, tiles[i, j].color)
       end
   end
   newgame()
   win = GtkWindow("Number Triplets Game", w, h)
   vbox = GtkBox(:v)
   newgamebutton = GtkButton("New Game")
   signal_connect(w -> newgame(), newgamebutton, "clicked")
   prompt1, prompt2 = "Choose a Red Button", "Choose a Yellow or Blue Button"
   promptlabel = GtkLabel(prompt1)
   grid = GtkGrid()
   set_gtk_property!(grid, :column_homogeneous, true)
   set_gtk_property!(grid, :row_homogeneous, true)
   bstate, lasttile, lastx, lasty = 0, nothing, 0, 0
   function process_click(i, j)
       if bstate == 0
           if tiles[i, j].label != ""
               bstate, lasttile, lastx, lasty = 1, tiles[i, j], i, j
               GAccessor.text(promptlabel, "                               ")
               GAccessor.text(promptlabel, "(Moves: $moves) $prompt2")
           end
       elseif bstate == 1
           if tiles[i, j].color in ["yellow", "blue"] && lasttile != nothing &&
               hasclearpath(lastx, lasty, i, j)
               moves += 1
               lasttile = basetiles[i, j]
               tiles[i, j] = lasttile
               tiles[lastx, lasty], tiles[i, j] = basetiles[lastx, lasty], tiles[lastx, lasty]
               setbuttonview()
               if map(x -> x.label, tiles) == labels
                   !won && info_dialog("You have won the game. (Moves: $moves)")
                   won = true
               end
           end
           GAccessor.text(promptlabel, "                               ")
           GAccessor.text(promptlabel, prompt1)
           bstate, lasttile, lastx, lasty = 0, nothing, 0, 0
       end
   end
   function hasclearpath(x1, y1, x2, y2)
       dx, dy = x2 - x1, y2 -y1
       ((dx == 0 && dy == 0) || dx * dy != 0) && return false # rook-format moves
       if dx != 0
           for k in x1+sign(dx):x2
               tiles[k, y2].color != "blue" && return false
           end
       elseif dy != 0
           for k in y1+sign(dy):y2
               !(tiles[x2, k].color in ["yellow", "blue"]) && return false
           end
       end
       return true
   end
   for i in 1:xgrid, j in 1:ygrid
       signal_connect(w -> process_click(i, j), buttons[i, j], "clicked")
       grid[i, j] = buttons[i, j]
   end
   push!(vbox, promptlabel)
   push!(vbox, newgamebutton)
   push!(vbox, grid)
   push!(win, vbox)
   done = Condition()
   endit(w) = notify(done)
   signal_connect(endit, win, :destroy)
   showall(win)
   wait(done)

end

NumberTripletsApp() </lang>

Perl

Change: Once a red square is selected, it stays selected (until you select a different red square). This means after the selection, it only takes one click to move squares instead of two. Update: I got tired of doing multiple clicks to move a square, so this version allows one click to move anywhere multiple clicks would allow. Update: disabled buttons that are invalid destinations. <lang perl>#!/usr/bin/perl

use strict; use warnings; use Tk; use List::Util qw( shuffle );

my (@buttons, $pressed); my $mw = MainWindow->new; $mw->geometry( '+800+300' ); $mw->title( 'Number Triplets Game - RosettaCode' ); my $grid = $mw->Frame->pack; for my $row ( 0 .. 4 )

 {
 for my $col ( 0 .. 8 )
   {
   my $color = $row ? $col & 1 ?
     'yellow' : 0 < $col < 8 ? 'white' : 'black' : 'blue';
   push @buttons, [ $grid->Button(-text => ' ',
     -bg => $color, -fg => 'white',
     -width => 2, -height => 2, -font => 'timesbold 20',
     -command => sub { press($row * 10 + $col) },
     )->grid(-row => $row, -column => $col), $color, 0, $row * 10 + $col ];
   }
 push @buttons, [0, 'green', 0];
 }

$mw->Button(-text => 'New Game', -command => \&newgame, -height => 2,

 -bg => 'black', -fg => 'magenta', -font => 'timesbold 20',
 )->pack(-fill => 'x');

newgame(); MainLoop;

sub reach

 {
 my $button = shift;
 my $field = join , map { $_->[0] ? $_->[2] ? $_->[2] :
   $_->[1] =~ /blue|yellow/ ? ' ' : '#': "\n" } @buttons;
 substr $field, $button->[3], 1, '-';
 1 while $field =~ s/-(|.{9}) | (|.{9})-/-$+-/s;
 $_->[0] and $_->[0]->configure(-state => $_->[2] ||
   substr($field, $_->[3], 1) eq '-' ? 'normal' : 'disabled') for @buttons;
 }

sub press

 {
 my ($n) = @_;
 my $button = $buttons[$n];
 if( $button->[2] ) # has number, change selected
   {
   reach( $pressed = $button );
   }
 else # swap
   {
   $button->[2] = $pressed->[2];
   $pressed->[2] = 0;
   $button->[0]->configure(-bg => 'red3', -text => $button->[2]);
   $pressed->[0]->configure(-bg => $pressed->[1], -text => ' ');
   reach( $pressed = $button );
   }
 }

sub newgame

 {
 $_->[0] && $_->[0]->configure(-bg => $_->[1], -text => ' ',
   -state => 'disabled'), $_->[2] = 0, for @buttons; # clear
 my @valid = shuffle grep $_->[1] =~ /blue|yellow/, @buttons;
 $valid[0][0]->configure(-bg => 'red3', -text => $_, -state => 'normal'),
   $valid[0][2] = $_, shift @valid for qw(1 2 3 4) x 3;
 }</lang>

Phix

Library: Phix/online

You can run this online here. Resize to taste. Use the cursor keys to move (green focus box), space to pick up or drop a tile (carried numbers turn orange). You can also use the mouse: hover on a neighbour to "lure" the focus box to follow you, click on a focused tile to pick up or drop it. Moving the mouse about or clicking on distant squares (as in more than one square away horizontally or vertically from the green focus box) achieves nothing. Any tile being carried is automatically dropped whenever a bump occurs. When finished, keying space starts another game.

--
-- demo\rosetta\NumberTripletsGame.exw
-- ===================================
--
with javascript_semantics
include pGUI.e
Ihandle dlg, canvas
cdCanvas cdcanvas

constant title = "Number Triplets Game",
         background = {"BBBBBBBBB",
                       "KYPYPYPYK",
                       "KYPYPYPYK",
                       "KYPYPYPYK",
                       "KYPYPYPYK"},
         ccc = {{'B',CD_BLUE},
                {'K',CD_BLACK},
                {'Y',CD_YELLOW},
                {'P',CD_PARCHMENT},
                {'R',CD_RED}},
         {colour_codes,colours} = columnize(ccc),
         target = {"         ",
                   "         ",
                   " 1 2 3 4 ",
                   " 1 2 3 4 ",
                   " 1 2 3 4 "}
sequence board, -- (as per target)
         board_row_centres,
         board_col_centres
integer cursor_row,
        cursor_col
bool bTileSelected,
     bGameOver

procedure new_game()
    board = deep_copy(target)
    string s = shuffle("111222333444")
    integer sdx = 1
    for row=3 to 5 do
        for col=2 to 8 by 2 do
            board[row][col] = s[sdx]
            sdx += 1
        end for
    end for
    cursor_row = 1
    cursor_col = 1
    bTileSelected = false
    bGameOver = false
    board_row_centres = {}
    board_col_centres = {}
    IupSetStrAttribute(dlg,"TITLE",title)
end procedure

function redraw_cb(Ihandle /*ih*/)
    integer {width, height} = IupGetIntInt(canvas, "DRAWSIZE"),
            tilew = floor(width/9)-1,
            tileh = floor(height/5)-1,
            leftm = floor((width-(tilew*9+10))/2),
            topm = floor((height-(tileh*5+6))/2)
    cdCanvasActivate(cdcanvas)
    cdCanvasSetBackground(cdcanvas, CD_DARK_GREY)
    cdCanvasClear(cdcanvas)
    cdCanvasFont(cdcanvas, "Helvetica", CD_BOLD, -floor(tileh/2))
    cdCanvasSetTextAlignment(cdcanvas,CD_CENTER)
    board_row_centres = repeat(0,5)
    board_col_centres = repeat(0,9)
    for row=1 to 5 do
        integer y = height-(topm+tileh*(row-1)+row),
                y2 = y-tileh,
                cy = y-floor(tileh/2)
        board_row_centres[row] = cy
        for col=1 to 9 do
            integer clr = background[row][col]
            if board[row][col]!=' ' then
                clr = 'R'
            end if
            clr = colours[find(clr,colour_codes)]
            cdCanvasSetForeground(cdcanvas, clr)
            integer x = leftm+tilew*(col-1)+col,
                    x2 = x+tilew,
                    cx = x+floor(tilew/2)
            board_col_centres[col] = cx
            cdCanvasBox(cdcanvas,x,x2,y2,y)
            bool bCurrent = (row==cursor_row and col==cursor_col)
            if bCurrent then
                cdCanvasSetForeground(cdcanvas, CD_GREEN)
                cdCanvasSetLineWidth(cdcanvas, 4)
                cdCanvasRect(cdcanvas,x+4,x2-5,y-5,y2+4)
            end if
            integer ch = board[row][col]
            if ch!=' ' then
                clr = iff(bCurrent and bTileSelected?CD_ORANGE:CD_PARCHMENT)
                cdCanvasSetForeground(cdcanvas, clr)
                cdCanvasText(cdcanvas,cx,cy,""&ch)
            end if
        end for
    end for
    cdCanvasFlush(cdcanvas)
    return IUP_DEFAULT
end function

function map_cb(Ihandle /*ih*/)
    atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
    IupGLMakeCurrent(canvas)
    if platform()=JS then
        cdcanvas = cdCreateCanvas(CD_IUP, canvas)
    else
        cdcanvas = cdCreateCanvas(CD_GL, "10x10 %g", {res})
    end if
    cdCanvasSetBackground(cdcanvas, CD_PARCHMENT)
    return IUP_DEFAULT
end function

function canvas_resize_cb(Ihandle /*canvas*/)
    integer {canvas_width, canvas_height} = IupGetIntInt(canvas, "DRAWSIZE")
    atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
    cdCanvasSetAttribute(cdcanvas, "SIZE", "%dx%d %g", {canvas_width, canvas_height, res})
    return IUP_DEFAULT
end function

procedure move(integer dy, dx, bool bValid)
    if not bValid then
        bTileSelected = false
    else
        integer ny = cursor_row+dy,
                nx = cursor_col+dx
        if bTileSelected then
            integer tile = board[cursor_row][cursor_col]
            if board[ny][nx]!=' ' then
                bTileSelected = false
            else
                board[cursor_row][cursor_col] = ' '
                board[ny][nx] = tile
                bGameOver = board=target
                if bGameOver then
                    IupSetStrAttribute(dlg,"TITLE",title & " - GAME OVER")
                    bTileSelected = false
                end if
            end if
        end if
        cursor_row = ny
        cursor_col = nx
    end if
end procedure

function key_cb(Ihandle /*ih*/, atom c)
    if c=K_ESC then return IUP_CLOSE end if -- (standard practice for me)
    if c=K_F5 then return IUP_DEFAULT end if -- (let browser reload work)
    if not bGameOver then
        if    c=K_LEFT  then move( 0,-1, cursor_row=1 and cursor_col>1 )
        elsif c=K_RIGHT then move( 0, 1, cursor_row=1 and cursor_col<9 )
        elsif c=K_DOWN  then move( 1, 0, cursor_row<5 and even(cursor_col) )
        elsif c=K_UP    then move(-1, 0, cursor_row>1 )
        elsif c=K_SP then
            if bTileSelected then
                bTileSelected = false
            elsif board[cursor_row][cursor_col]!=' ' then
                bTileSelected = true
            end if
        end if
    elsif c=K_SP then
        new_game()
    end if
    IupRedraw(canvas)
    return IUP_CONTINUE
end function

function check_position(integer px, py)
--
-- convert x,y mouse move/click to row/col
--
    if not bGameOver 
    and board_row_centres!={} then -- (when started with mouse cursor on-board)
        integer myrow = 1,
                mxcol = 9
        for row=1 to 4 do
            if py>(board_row_centres[row]+board_row_centres[row+1])/2 then
                myrow = 6-row
                exit
            end if
        end for
        for col=1 to 8 do
            if px<(board_col_centres[col]+board_col_centres[col+1])/2 then
                mxcol = col
                exit
            end if
        end for
        return {myrow, mxcol}
    end if
    return {0,0}
end function

function motion_cb(Ihandle canvas, integer x, y, atom /*pStatus*/)
    integer {myrow, mxcol} = check_position(x,y),
            dy = cursor_row-myrow,
            dx = cursor_col-mxcol
    if (dy=0 and abs(dx)=1)
    or (dx=0 and abs(dy)=1) then
        integer c = iff(dy=0?iff(dx=+1?K_LEFT:K_RIGHT)
                            :iff(dy=-1?K_DOWN:K_UP))
        return key_cb(canvas, c)
    end if  
    return IUP_CONTINUE
end function

function button_cb(Ihandle canvas, integer button, pressed, x, y, atom /*pStatus*/)
    if button=IUP_BUTTON1 and not pressed then      -- (left button released)
        integer {myrow, mxcol} = check_position(x,y)
        if myrow = cursor_row
        and mxcol = cursor_col then
            return key_cb(canvas, K_SP)     -- pickup/drop
        else
            -- fallback in case no hover, eg on tablet/phone(?)
            return motion_cb(canvas,x,y,NULL)
        end if
    end if
    return IUP_CONTINUE
end function

procedure main()
    IupOpen()
    canvas = IupGLCanvas("RASTERSIZE=640x340")
    sequence cb = {"MAP_CB", Icallback("map_cb"),
                   "ACTION", Icallback("redraw_cb"),
                   "RESIZE_CB", Icallback("canvas_resize_cb"),
                   "MOTION_CB", Icallback("motion_cb"),
                   "BUTTON_CB", Icallback("button_cb")}
    IupSetCallbacks(canvas, cb)
    dlg = IupDialog(canvas,`TITLE="%s"`,{title})
    IupSetCallback(dlg, "KEY_CB", Icallback("key_cb"))
    new_game()
    IupShow(dlg)
    IupSetAttribute(canvas, "RASTERSIZE", NULL) -- (allow full resize)
    if platform()!=JS then
        IupMainLoop()
        IupClose()
    end if
end procedure
main()

Ring


Number Triplets Game in Ring - video

<lang ring>

  1. Project : Number Triplets Game
  2. Date  : 19/08/2021-06:30:09
  3. Author  : Gal Zsolt (~ CalmoSoft ~)
  4. Email  : <calmosoft@gmail.com>

load "stdlib.ring" load "guilib.ring"

C_GAMETITLE = 'Number Triplets Game' C_WINDOWBACKGROUND = "background-color: gray;"

if isMobile()

  C_LABELFONTSIZE 	= "font-size:120px;"
  C_BUTTONFONTSIZE 	= "font-size:160px;"

else

  C_LABELFONTSIZE 	= "font-size:50px;"
  C_BUTTONFONTSIZE 	= "font-size:80px;"

ok

C_NEWGAMESTYLE = 'color:magenta;background-color:rgb(50,50,50);border-radius:17px;' + C_LABELFONTSIZE C_BUTTONTOP = 'border-radius:17px;color:white; background-color: blue ;' + C_BUTTONFONTSIZE C_BUTTONREDSTYLE = 'border-radius:17px;color:white; background-color: red ;' + C_BUTTONFONTSIZE C_BUTTONSTYLE = 'border-radius:17px;color:white; background-color: yellow ;' + C_BUTTONFONTSIZE C_EMPTYBUTTONSTYLE = 'border-radius:17px;color:black; background-color: white ;' + C_BUTTONFONTSIZE C_BUTTONBLACKSTYLE = 'border-radius:17px;color:black; background-color: black ;' + C_BUTTONFONTSIZE

C_LAYOUTSPACING = 10

sizex = 9 sizey = 5 flag = 0 x1 = 0 x2 = 0 y1 = 0 y2 = 0

button = newlist(sizex,sizey) LayoutButtonRow = list(sizey)

app = new qApp {

     StyleFusion()
     processevents()
     win = new qWidget() {

setWindowTitle(C_GAMETITLE) setgeometry(100,100,800,600) setminimumwidth(300) setminimumheight(300) if not isMobile() grabkeyboard() ok setstylesheet(C_WINDOWBACKGROUND) move(490,100) newgame = new QLabel(win) { setalignment(Qt_AlignHCenter | Qt_AlignVCenter) setstylesheet(C_NEWGAMESTYLE) settext('New Game') myfilter = new qallevents(newgame) myfilter.setMouseButtonPressEvent("pbegin()") installeventfilter(myfilter) } for n = 1 to sizex for m = 1 to sizey button[n][m] = new QPushButton(win) next

           next
           LayoutGrid = new QGridLayout() {

setSpacing(C_LAYOUTSPACING) for n = 1 to sizey for m = 1 to sizex AddWidget(button[m][n],n-1,m-1,0) next next }

           LayoutButtonMain = new QVBoxLayout() {

AddLayout(LayoutGrid) AddWidget(newGame) }

           setLayout(LayoutButtonMain)

show() pbegin()

     }
     exec()
     }

func pbegin

    flag = 0
    for n = 1 to sizex

for m = 1 to sizey if n%2 = 1 button[n][m].setstylesheet(C_EMPTYBUTTONSTYLE) button[n][m].setenabled(false) else button[n][m].setstylesheet(C_BUTTONSTYLE) ok if n = 1 or n = 9 button[n][m].setstylesheet(C_EMPTYBUTTONSTYLE) button[n][m].setenabled(false) button[n][m].settext() ok if m = 1 button[n][m].setstylesheet(C_BUTTONTOP) button[n][m].setenabled(true) button[n][m].settext() ok button[n][m] { setclickevent("keypress(" + string(n) + "," + string(m) + ")") } next next for row = 2 to sizey button[1][row].setstylesheet(C_BUTTONBLACKSTYLE) button[9][row].setstylesheet(C_BUTTONBLACKSTYLE) next numRand = list(4) pRandom()

func pRandom

    for n = 1 to sizex

for m = 1 to sizey button[n][m].settext("") next

    next
    checkNum = []
    randNum = list(4)
    for p = 1 to 4

randNum[p] = 0

    next
    while true

xRand = random(sizex-1) + 1 yRand = random(sizey-1) + 1 numRand = random(3)+1 str = " " if xRand%2 = 0 and yRand != 1 if randNum[numRand] < 3 numStr = string(xRand)+string(yRand) ind = find(checkNum,numStr) if ind < 1 add(checkNum,numStr) temp = randNum[numRand] randNum[numRand] = temp + 1 button[xRand][yRand].settext(str+string(numRand)+str) button[xRand][yRand].setstylesheet(C_BUTTONREDSTYLE) ok ok ok if randNum[1] = 3 and randNum[2] = 3 and randNum[3] = 3 and randNum[4] = 3 exit ok end

func keyPress x,y

    flag++
    switch flag

on 1 x1 = x y1 = y on 2 x2 = x y2 = y flag = 0 pMove(x1,y1,x2,y2)

    off
       

func pMove col1,row1,col2,row2

    if col1!=0 and col2!=0 and row1!=0 and row2!=0 

if ( col1=col2 and row2=row1+1 and col1%2=0 ) or ( col1=col2 and row2=row1-1 and col1%2=0 ) or ( row1=row2 and col2=col1+1 and row1=1 ) or ( row1=row2 and col2=col1-1 and row1=1 ) or ( row1=row2 and col2=col1-1 and row1=1 )

          temp = button[col1][row1].text()

if temp!= "" button[col2][row2].setstylesheet(C_BUTTONREDSTYLE) button[col2][row2].settext(temp) if row1 = 1 button[col1][row1].setstylesheet(C_BUTTONTOP) else button[col1][row1].setstylesheet(C_BUTTONSTYLE) ok button[col1][row1].settext("") ok ok

    ok

</lang>

Wren

Library: DOME
Library: Wren-ellipse
Library: Go-fonts

It's not currently possible to run a DOME application online in a browser but the following is designed to look and work more or less like the Ring entry. The only significant difference is that when a red button is selected then, to give some visual indication of this, it acquires a white border which disappears when the button is moved or a different red button is selected.

As it's unclear what the initial state of the game should be, this places the red buttons in their final positions but randomizes their labels each time the game is started. <lang ecmascript>import "dome" for Window import "graphics" for Canvas, Color, Font import "audio" for AudioEngine import "input" for Mouse import "random" for Random import "./ellipse" for Button

var Rand = Random.new()

class NumberTriplets {

   construct new() {
       Window.resize(900, 600)
       Canvas.resize(900, 600)
       Window.title = "Number triplets game"
       // see Go-fonts page
       Font.load("Go-Regular30", "Go-Regular.ttf", 30)
       Canvas.font = "Go-Regular30"
       // download from https://soundbible.com/509-Mouse-Double-Click.html
       AudioEngine.load("click", "mouse_click.wav")
   }

   init() {
       newGame()
   }

   newGame() {
       Canvas.cls(Color.darkgray)
       _btnMap = List.filled(5, null)
       for (r in 0..4) _btnMap[r] = List.filled(9, null)
       for (c in 0..8) _btnMap[0][c] = [Color.blue, 0]
       for (r in 1..4) {
           for (c in [0, 8]) _btnMap[r][c] = [Color.black, 0]
           for (c in [1, 3, 5, 7]) {
               _btnMap[r][c] = (r == 1) ? [Color.yellow, 0] : [Color.red, 0]
           }
           for (c in [2, 4, 6]) _btnMap[r][c] = [Color.white, 0]
       }
       _sel = [-2, -2] // denotes no button selected
       _btns = List.filled(5, null)
       for (r in 0..4) {
           _btns[r] = List.filled(9, null)
           for (c in 0..8) {
               _btns[r][c] = Button.square(c * 100 + 50, r * 100 + 50, 80)
               _btns[r][c].drawfill(_btnMap[r][c][0])
           }
       }
       _btnNew = Button.new(450, 550, 860, 80)
       _btnNew.drawfill(Color.black)
       Canvas.print("New Game", 370, 530, Color.purple)
       // randomize labels on red buttons
       for (r in 2..4) {
           var labels = Rand.shuffle([1, 2, 3, 4])
           var i = 0
           for (c in [1, 3, 5, 7]) {
               var b = _btns[r][c]
               var lbl = _btnMap[r][c][1] = labels[i]
               Canvas.print(lbl.toString, b.cx - 10, b.cy - 20, Color.white)
               i = i + 1
           }
       }
   }

   update() {
       if (Mouse["left"].justPressed) {
           AudioEngine.play("click")
           var x = Mouse.x
           var y = Mouse.y
           var r = (y / 100).floor
           if (r == 5) {
               if (_btnNew.contains(x, y)) newGame()
               return
           }
           var m = y % 100
           if (m < 10 || m > 90) return
           var c = (x / 100).floor
           var n = x % 100
           if (n < 10 || n > 90) return
           var b = _btnMap[r][c]
           if (b[0] == Color.black || b[0] == Color.white) return
           if (b[0] == Color.red) {
              if (_sel[0] != -2) _btns[_sel[0]][_sel[1]].draw(Color.red)
              _sel = [r, c]
              _btns[r][c].draw(Color.white)
           } else if (b[0] == Color.yellow) {
               if (_sel[0] == r - 1 || _sel[0] == r + 1) {
                   var btn = _btns[r][c]
                   btn.drawfill(Color.red)
                   var lbl = _btnMap[_sel[0]][c][1]
                   _btnMap[r][c] = [Color.red, lbl]
                   Canvas.print(lbl.toString, btn.cx - 10, btn.cy - 20, Color.white)
                   if (_sel[0] == 0) {
                       _btns[0][c].drawfill(Color.blue)
                       _btnMap[0][c] = [Color.blue, 0]
                   } else {
                       _btns[_sel[0]][c].drawfill(Color.yellow)                
                       _btnMap[_sel[0]][c] = [Color.yellow, 0]
                   }
                   _sel = [-2, -2]
               }
           } else if (b[0] == Color.blue) {
               if (_sel[0] == r + 1 || _sel[1] == c - 1 || _sel[1] == c + 1) {
                   var btn = _btns[r][c]
                   btn.drawfill(Color.red)
                   var lbl = _btnMap[_sel[0]][_sel[1]][1]
                   _btnMap[r][c] = [Color.red, lbl]
                   Canvas.print(lbl.toString, btn.cx - 10, btn.cy - 20, Color.white)
                   if (_sel[0] == 0) {
                       _btns[0][_sel[1]].drawfill(Color.blue)
                       _btnMap[0][_sel[1]] = [Color.blue, 0]
                   } else if (_sel[0] > 0) {
                       _btns[_sel[0]][_sel[1]].drawfill(Color.yellow)
                       _btnMap[_sel[0]][_sel[1]] = [Color.yellow, 0]
                   }
                   _sel = [-2, -2]
               }
           }
       }
   }

   draw(alpha) {}

}

var Game = NumberTriplets.new()</lang>