Number triplets game

From Rosetta Code
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.

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()

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.

#!/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;
  }

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

# Project : Number Triplets Game
# Date    : 19/08/2021-06:30:09
# Author  : Gal Zsolt (~ CalmoSoft ~)
# 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

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.

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()