T-SQL Code for the popular game of "Tower of Hanoi", that can be played in Microsoft SQL Server, Azure Data Studio or any other T-SQL editor with support of query execution.
About the game
Tower of Hanoi is puzzle game consisting of three rods and number of rings (disks) of different size (diameters). Rings are slide into any rod. Game begins with all rings stacked on one rod, ordered by descending size - from smallest on top, to biggest at the bottom.
The purpose of the game is to move the entire stack of rings from first to last rod, ordered by decreasing size.
Two simple rules apply:
1. only one ring can be moved at the time
2. bigger ring can not be stacked on smaller ring
3. each move consists of taking upper most ring and placing it on the other rod (on top of another stacked rings or on empty rod).
T-SQL Procedures for the game
Game has two simple procedures. The initialization of the table for the background information store is created with dbo.INIT_Hanoi procedure.
CREATE OR ALTER PROCEDURE dbo.INIT_Hanoi /************************************************************** Procedure: dbo.INIT_Hanoi Create Date: 2021-12-25 Author: Tomaz Kastrun Description: Creates a table that stores the number of rings used in the game with three rods. Table name is dbo.Hanoi and is used to to store the moves. Procedure output: [dbo].[Hanoi] Parameter(s): @rings - Number of rings; e.g.: 5 = 5 rings on 3 rods; Type: TINYINT (max 255 rings!) Usage: EXEC dbo.INIT_Hanoi @rings = 5 ChangeLog: ToDO: Align Drawing! ************************************************************* */ @rings TINYINT AS BEGIN SET NOCOUNT ON; DECLARE @j INT = 1 DROP TABLE IF EXISTS dbo.Hanoi; DECLARE @TableCreate NVARCHAR(1000) = 'DROP TABLE IF EXISTS dbo.Hanoi; CREATE TABLE dbo.Hanoi ( ID TINYINT IDENTITY(1,1) NOT NULL ,T1 TINYINT NOT NULL ,T2 TINYINT NOT NULL ,T3 TINYINT NOT NULL ) ' WHILE (@rings >= @j) BEGIN SET @TableCreate = @TableCreate + ' INSERT INTO dbo.Hanoi(T1, T2, T3) VALUES ('+CAST(@j AS varchar(10))+',0,0) ' SET @j = @j+1 END EXEC sp_executesql @tableCreate DECLARE @max INT = @rings*4 SELECT REPLICATE(' ',(@max - T1*2)/2) + REPLICATE('#', T1*2) + REPLICATE(' ',(@max - T1*2)/2) AS T1 ,REPLICATE(' ',(@max - T2*2)/2) + REPLICATE('#', T2*2) + REPLICATE(' ',(@max - T2*2)/2) AS T2 ,REPLICATE(' ',(@max - T3*2)/2) + REPLICATE('#', T3*2) + REPLICATE(' ',(@max - T3*2)/2) AS T3 FROM hanoi ORDER BY ID ASC END; GO
And the play procedure for moving and stacking the rings around the rods, done with dbo.PLAY_Hanoi
CREATE OR ALTER PROCEDURE [dbo].[PLAY_Hanoi] /************************************************************** Procedure: dbo.PLAY_Hanoi Create Date: 2021-12-25 Author: Tomaz Kastrun Description: Creates a table that stores the number of rings used in the game with three rods. Table name is dbo.Hanoi and is used to to store the moves. Procedure output: [dbo].[Hanoi] Parameter(s): @from - rod number taking the first ring @to - rod number putting the same ring Usage: EXEC dbo.PLAY_Hanoi @from = 1 ,@to = 2 ToDO: - Align Drawing of tower! - Stop / finish the game - Rings different ASCII Char! ************************************************************* */ @from INT ,@to INT AS BEGIN SET NOCOUNT ON; -- internal values DECLARE @from_variable VARCHAR(10) = (select column_name from information_Schema.columns where table_name = 'hanoi' and table_Schema = 'dbo' and ordinal_position = (@from + 1)) print @from_variable DECLARE @to_variable VARCHAR(10) = (select column_name from information_Schema.columns where table_name = 'hanoi' and table_Schema = 'dbo' and ordinal_position = (@to + 1)) print @to_variable -- FROM position DECLARE @from_position NVARCHAR(1000) SET @from_position = 'SELECT top 1 ID FROM dbo.hanoi where '+@from_Variable+' <> 0 order by id asc' DROP TABLE IF EXISTS #from_pos CREATE table #from_pos (val int) INSERT INTO #from_pos EXEC sp_executesql @from_position -- FROM value DECLARE @from_value NVARCHAR(1000) SET @from_value = 'SELECT top 1 '+@from_variable+' FROM dbo.hanoi where '+@from_Variable+' <> 0 order by id asc' DROP TABLE IF EXISTS #from_val CREATE table #from_val (val int) INSERT INTO #from_val EXEC sp_executesql @from_value IF (SELECT COUNT(*) FROM #from_val) = 0 BEGIN INSERT INTO #from_val VALUES (0) END -- TO position DECLARE @to_position NVARCHAR(1000) SET @to_position = 'SELECT top 1 ID FROM dbo.hanoi where '+@to_variable+' = 0 order by id desc' DROP TABLE IF EXISTS #to_pos CREATE table #to_pos (val int) INSERT INTO #to_pos EXEC sp_executesql @to_position -- TO value DECLARE @to_value NVARCHAR(1000) SET @to_value = 'SELECT top 1 '+@to_variable+' FROM dbo.hanoi where '+@to_variable+' = 0 order by id desc' DROP TABLE IF EXISTS #to_val CREATE table #to_val (val int) INSERT INTO #to_val EXEC sp_executesql @to_value -- TO Prev Value DECLARE @prev_to_val NVARCHAR(1000) SET @prev_to_val = 'select top 1 '+@to_variable+' from hanoi where '+@to_variable +' <> 0 order by id asc' DROP TABLE IF EXISTS #to_prev_val CREATE table #to_prev_val (val int) INSERT INTO #to_prev_val EXEC sp_executesql @prev_to_val -- number of rings! declare @rings int = (select COUNT(*) from dbo.hanoi) declare @max int = @rings*4 --- internal update -- add rules for update!!!! IF ((SELECT ISNULL(val,0) FROM #to_prev_val) < (SELECT val FROM #from_val)) BEGIN SELECT 'Wrong Move' END ELSE BEGIN IF ((SELECT ISNULL(val,0) FROM #to_val) = 0 AND (SELECT ISNULL(val,0) FROM #from_val) = 0) BEGIN SELECT 'Invalid Move' END ELSE BEGIN --update FROM pos/value DECLARE @update_from NVARCHAR(1000) SET @update_from = 'update dbo.hanoi set '+@from_variable+' = (select 0 ) WHERE ID = (SELECT val FROM #from_pos) ' EXEC sp_executesql @update_from --update TO pos/value DECLARE @update_to NVARCHAR(1000) SET @update_to = 'update dbo.hanoi set '+@to_variable+' = (select val from #from_Val) WHERE ID = (SELECT val FROM #to_pos)' EXEC sp_executesql @update_to END END SELECT REPLICATE(' ',(@max - T1*2)/2) + REPLICATE('#', T1*2) + REPLICATE(' ',(@max - T1*2)/2) AS T1 ,REPLICATE(' ',(@max - T2*2)/2) + REPLICATE('#', T2*2) + REPLICATE(' ',(@max - T2*2)/2) AS T2 ,REPLICATE(' ',(@max - T3*2)/2) + REPLICATE('#', T3*2) + REPLICATE(' ',(@max - T3*2)/2) AS T3 FROM dbo.hanoi ORDER BY ID ASC -- check Tower 2 and Tower 3 DECLARE @t2 INT = (SELECT COUNT(T2) FROM Hanoi WHERE T2 <> 0) DECLARE @t3 INT = (SELECT COUNT(T3) FROM Hanoi WHERE T3 <> 0) IF (@T2 = @rings OR @T3 = @rings) BEGIN SELECT 'Game Won!' -- Initialize New Game EXEC dbo.INIT_Hanoi @rings END END; GO
Playing the game
After running the content of Tower_Hanoi.sql file (creating two procedures dbo.INIT_Hanoi and dbo.Play_Hanoi ), start the game with:
EXEC dbo.INIT_Hanoi @rings = 4
And continue playing the game:
EXEC dbo.PLAY_Hanoi @from = 1 ,@to = 2; GO EXEC dbo.PLAY_Hanoi @from = 1 ,@to = 3; GO
Opening the game in Azure Data Studio or in SSMS, the outlook do the game should be:
Autosolver procedure
Solving Tower of Hanoi game can be by a procedure, that automatically finds solution:
CREATE OR ALTER PROCEDURE dbo.AutoSolver /************************************************************** Procedure: dbo.AutoSolver Create Date: 2021-12-26 Author: Tomaz Kastrun Description: Initialize and solves the game Tower of Hanoi for the given number of rings. All steps are temporarely stored in log. Procedure output: [dbo].[Hanoi_log] Parameter(s): @rings - number of rings Usage: EXEC dbo.Autosolver @rings = 4 ToDO: - Optimization for end result on rod 2 or 3 ************************************************************* */ @rings TINYINT AS BEGIN drop table if exists dbo.hanoi_log; CREATE TABLE dbo.hanoi_log (id int identity(1,1) NOT NULL ,rodd varchar(10) ,f_rod int ,t_rod int ) EXEC dbo.INIT_Hanoi @rings = @rings; -- init set of @T2_count and @T3_count DECLARE @t2_count INT = -1 DECLARE @t3_count INT = -1 DECLARE @from INT DECLARE @to INT WHILE (@t2_count <= @rings) OR (@t3_count <= @rings) BEGIN DECLARE @t1 INT = (Select TOP 1 ISNULL(t1,0) from hanoi WHERE t1 <> 0 ORDER BY ID ASC) DECLARE @t2 INT = (Select TOP 1 ISNULL(t2,0) from hanoi WHERE t2 <> 0 ORDER BY ID ASC) DECLARE @t3 INT = (Select top 1 ISNULL(t3,0) from hanoi WHERE t3 <> 0 ORDER BY ID ASC) INSERT INTO dbo.hanoi_log (rodd, f_rod, t_rod) SELECT TOP 1 cast(right(t.rod,1) as varchar(10)) + ';' + cast(right(f.rod,1) as varchar(10)) as rodd ,right(f.rod,1) AS from_rod ,right(t.rod,1) as To_rod --,CAST(right(f.rod,1) as int) + cast(right(t.rod,1) as int) as suma_f FROM ( SELECT ISNULL(@t1,0) as val, 't1' as rod, 'from' as pot union SELECT ISNULL(@t2,0), 't2' as rod, 'from' as pot union SELECT ISNULL(@t3,0), 't3' as rod, 'from' as pot ) as f cross join ( SELECT ISNULL(@t1,0) as val, 't1' as rod, 'to' as pot union SELECT ISNULL(@t2,0), 't2' as rod, 'to' as pot union SELECT ISNULL(@t3,0), 't3' as rod, 'to' as pot ) as t WHERE f.rod <> t.rod AND f.val <> 0 AND (t.val > f.val OR t.val = 0) AND CAST(right(f.rod,1) as varchar(10)) + ';' + cast(right(t.rod,1) as varchar(10)) NOT IN (SELECT rodd from dbo.hanoi_log where id = (SELECT max(id) from hanoi_log)) --last AND CAST(right(f.rod,1) as varchar(10)) + ';' + cast(right(t.rod,1) as varchar(10)) NOT IN (SELECT rodd from dbo.hanoi_log where id = (SELECT max(id)-1 from hanoi_log)) --before_last ORDER BY CAST(right(f.rod,1) as int) + cast(right(t.rod,1) as int) desc DECLARE @max_id INT = (select max(id) from dbo.hanoi_log) SET @from = (SELECT f_rod FROM dbo.hanoi_log WHERE id = @max_id) SET @to = (SELECT t_rod FROM dbo.hanoi_log WHERE id = @max_id) EXEC dbo.PLAY_Hanoi @from = @from ,@to = @to SET @t2_count = (SELECT COUNT(t2) FROM dbo.Hanoi WHERE t2 <> 0) SET @t3_count = (SELECT COUNT(t3) FROM dbo.Hanoi WHERE t3 <> 0) IF (@t2_count = 0) AND (@t3_count = 0) BEGIN BREAK END END END; GO
You can also use the SQLCMD. On your client machine, open CMD and navigate to your MSSQLServer folder (e.g.: C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER) and run the following CMD command (please note, I am using a named instance, hence -S switch):
sqlcmd -S .\MSSQLSERVER2019 -q "EXEC dbo.AutoSolver @rings = 4"
As always, the complete code is available at the Github: https://github.com/tomaztk/Tower_of_Hanoi_sql_game
Stay healthy and happy T-SQLing!
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.