15 puzzle game in 3D
- Task
Create either a 15 puzzle game or an 8 puzzle game in your language using cubes rather than squares to represent the tiles (including the blank tile). The 'cuboid look' may be simulated rather than using actual 3D graphics and the cubes may either rotate or remain static.
This should be a playable game as opposed to a program which automatically attempts to find a solution without user intervention.
- References
- Article: Wikipedia 15 puzzle.
- Video: 15 Puzzle Game in 3D for the Ring entry.
- Necessary files: Files for 15 Puzzle Game in 3D as used by the Ring entry.
Go
Despite the name of the task, it appears that the author's Ring entry is actually producing an '8 puzzle' game and that the 3D aspect is handled by replacing the usual squares with colored, numbered cubes which rotate in space.
The following produces instead a '15 puzzle' game and the 3D aspect is again handled by using colored, numbered cubes. However, the cubes do not rotate (I found this very distracting when playing the game) and the 3D look is simulated by drawing regular hexagons with inner lines drawn between the appropriate vertices to make them look like cubes. A white numberless cube represents the blank square in a normal game.
The game is controlled by the arrow keys (mouse movement is not supported) which move the white cube left, right, up or down exchanging positions with the colored cube already occupying that position. The number of moves is continually updated and, if the player is eventually successful in assembling the cubes in order, an appropriate message is displayed, the white cube changes to dark green and displays the number 16.
package main
import (
"fmt"
"github.com/gen2brain/raylib-go/raylib"
"math"
"math/rand"
"strconv"
"time"
)
var palette = []rl.Color{
rl.Blue,
rl.Green,
rl.Red,
rl.SkyBlue,
rl.Magenta,
rl.Gray,
rl.Lime,
rl.Purple,
rl.Violet,
rl.Pink,
rl.Gold,
rl.Orange,
rl.Maroon,
rl.Beige,
rl.Brown,
rl.RayWhite,
}
var (
screenWidth = int32(960)
screenHeight = int32(840)
radius = screenHeight / 14
fontSize = 2 * radius / 5
angle = math.Pi / 6
incr = 2 * angle
blank = 15
moves = 0
gameOver = false
)
var (
centers [16]rl.Vector2
cubes [16]int
)
func init() {
for i := 0; i < 16; i++ {
cubes[i] = i
}
}
func drawCube(n, pos int) {
r := float32(radius)
rl.DrawPoly(centers[pos], 6, r, 0, palette[n])
cx, cy := centers[pos].X, centers[pos].Y
for i := 1; i <= 5; i += 2 {
fi := float64(i)
vx := int32(r*float32(math.Cos(angle+fi*incr)) + cx)
vy := int32(r*float32(math.Sin(angle+fi*incr)) + cy)
rl.DrawLine(int32(cx), int32(cy), vx, vy, rl.Black)
}
ns := ""
if n < 15 || gameOver {
ns = strconv.Itoa(n + 1)
}
hr, er, tqr := r/2, r/8, 0.75*r
rl.DrawText(ns, int32(cx+hr-er), int32(cy), fontSize, rl.RayWhite)
rl.DrawText(ns, int32(cx-hr-er), int32(cy), fontSize, rl.RayWhite)
rl.DrawText(ns, int32(cx-er), int32(cy-tqr), fontSize, rl.RayWhite)
}
func updateGame() {
if gameOver {
return
} else if rl.IsKeyPressed(rl.KeyLeft) {
if blank%4 != 0 {
cubes[blank], cubes[blank-1] = cubes[blank-1], cubes[blank]
blank--
moves++
}
} else if rl.IsKeyPressed(rl.KeyRight) {
if (blank+1)%4 != 0 {
cubes[blank], cubes[blank+1] = cubes[blank+1], cubes[blank]
blank++
moves++
}
} else if rl.IsKeyPressed(rl.KeyUp) {
if blank > 3 {
cubes[blank], cubes[blank-4] = cubes[blank-4], cubes[blank]
blank -= 4
moves++
}
} else if rl.IsKeyPressed(rl.KeyDown) {
if blank < 12 {
cubes[blank], cubes[blank+4] = cubes[blank+4], cubes[blank]
blank += 4
moves++
}
}
}
func completed() bool {
for i := 0; i < 16; i++ {
if cubes[i] != i {
return false
}
}
palette[15] = rl.DarkGreen
gameOver = true
return true
}
func main() {
rand.Seed(time.Now().UnixNano())
rand.Shuffle(15, func(i, j int) {
cubes[i], cubes[j] = cubes[j], cubes[i]
})
rl.InitWindow(screenWidth, screenHeight, "15-puzzle game using 3D cubes")
rl.SetTargetFPS(60)
var x, y = float32(screenWidth) / 10, float32(radius)
for i := 0; i < 4; i++ {
cx := 2 * x * float32(i+1)
for j := 0; j < 4; j++ {
cy := (x + y) * float32(j+1)
centers[j*4+i] = rl.NewVector2(cx, cy)
}
}
for !rl.WindowShouldClose() {
rl.BeginDrawing()
rl.ClearBackground(rl.Black)
for i := 0; i < 16; i++ {
drawCube(cubes[i], i)
}
if !completed() {
m := fmt.Sprintf("Moves = %d", moves)
rl.DrawText(m, 4*int32(x), 13*int32(y), fontSize, rl.RayWhite)
} else {
m := fmt.Sprintf("You've completed the puzzle in %d moves!", moves)
rl.DrawText(m, 3*int32(x), 13*int32(y), fontSize, rl.RayWhite)
}
rl.EndDrawing()
updateGame()
}
rl.CloseWindow()
}
Phix
You can run this online here.
-- -- demo\rosetta\15_puzzle_game_in_3D.exw -- ===================================== -- -- Contains big chunks of demo\rosetta\DrawRotatingCube.exw -- and demo\pGUI\r3d.exw -- and demo\rosetta\Solve15puzzle_simple.exw -- (but modified to solve an 8 or 15-puzzle, -- and recursive ==> re-entrant iterative.) -- -- Use the up/down/left/right keys, -- or 's' to single-step-solve, or when solved re-scramble. -- or '8' to toggle between the 8 and 15 games. NB: it can -- spend a very long time just thinking about a 15-puzzle, -- but should still be perfectly playable, and keying 's' -- when thinking/before it has managed to find a solution -- should re-scramble to something hopefully a bit easier. -- with javascript_semantics without debug include pGUI.e include builtins\VM\pcmdlnN.e include builtins\pcurrdir.e include builtins\pgetpath.e include builtins\VM\pcfunc.e include builtins\pfile.e include builtins\VM\pprntfN.e include builtins\get_routine_info.e include builtins\VM\pTime.e include builtins\scanf.e include builtins\pdir.e include builtins\penv.e with debug constant title = "8 puzzle game in 3D", iterations_per_timeslice = 100_000 -- too low ==> too slow, but (obviously) -- too high ==> responsiveness:=sluggish -- 100K gives me around 20% CPU usage, -- that is, when in "thinking" mode. Ihandle dlg, canvas cdCanvas cd_canvas -- <copied from demo\rosetta\DrawRotatingCube.exw>: -- -- First, define 8 corners equidistant from {0,0,0}: -- -- 6-----2 -- 5-----1 3 -- 8-----4 -- -- ie the right face is 1-2-3-4 clockwise, and the left face -- is 5-6-7-8 counter-clockwise (unless using x-ray vision). -- enum X, Y, Z constant l = 100 constant corners = {{+l,+l,+l}, -- 1 (front top right) {+l,+l,-l}, -- 2 (back top "right") {+l,-l,-l}, -- 3 (back btm "right") {+l,-l,+l}, -- 4 (front btm right) {-l,+l,+l}, -- 5 (front top left) {-l,+l,-l}, -- 6 (back top "left") {-l,-l,-l}, -- 7 (back btm "left") {-l,-l,+l}} -- 8 (front btm left) -- I put left/right in quotes for the back face as a reminder -- those match the above diagram, but of course they would be -- swapped were you looking "at" the face/rotated it by 180. constant faces = {{CD_RED, 1,2,3,4}, -- right {CD_YELLOW, 1,5,6,2}, -- top {CD_DARK_GREEN, 1,4,8,5}, -- front {CD_BLUE, 2,3,7,6}, -- back {CD_WHITE, 3,4,8,7}, -- bottom {CD_ORANGE, 8,7,6,5}} -- left -- <this bit not from DrawRotatingCube> constant colours = {CD_DARK_CYAN, CD_DARK_YELLOW, CD_LIGHT_BLUE, CD_INDIGO, CD_ORANGE, CD_PURPLE, CD_MAGENTA, CD_OLIVE, CD_CYAN, CD_YELLOW, CD_BLUE, CD_VIOLET, CD_LIGHT_GREEN, CD_DARK_BLUE, CD_DARK_RED} constant m = l/2, n = l/4, k = l/8 -- (ps: the 6 has a curved top, whereas the 9 has a flat bottom.) constant digits = {{{n,n,l},{k,m,l},{-k,m,l},{-n,n,l},{-n,-n,l},{-k,-m,l},{k,-m,l},{n,-n,l},{n,n,l}}, --0 {{k,n,l},{0,m,l},{0,-m,l},{k,-m,l},{-k,-m,l}}, -- 1 {{n,n,l},{k,m,l},{-k,m,l},{-n,n,l},{n,-m,l},{-n,-m,l}}, -- 2 {{n,n,l},{k,m,l},{-k,m,l},{-n,n,l},{-k,0,l},{-n,-n,l},{-k,-m,l},{k,-m,l},{n,-n,l}}, --3 {{-n,0,l},{n,0,l},{0,m,l},{0,-m,l}}, -- 4 {{-k,m,l},{n,m,l},{n,0,l},{-k,0,l},{-n,-n,l},{-k,-m,l},{k,-m,l},{n,-n,l}}, -- 5 {{-n,n,l},{-k,m,l},{k,m,l},{n,n,l},{n,-n,l},{k,-m,l},{-k,-m,l},{-n,-n,l},{-k,0,l},{k,0,l},{n,-n,l}}, -- 6 {{n,m,l},{-n,m,l},{0,-m,l}}, -- 7 {{k,0,l},{n,n,l},{k,m,l},{-k,m,l},{-n,n,l},{-k,0,l},{-n,-n,l},{-k,-m,l},{k,-m,l},{n,-n,l},{k,0,l},{-k,0,l}}, --8 {{-n,n,l},{-k,0,l},{k,0,l},{n,n,l},{k,m,l},{-k,m,l},{-n,n,l},{-n,-n,l},{-k,-m,l},{k,-m,l}}} --9 -- initial rotation onto the required face constant irot = {{ 0,-90,0}, -- right {-90, 0,0}, -- top { 0, 0,0}, -- front {180, 0,0}, -- back { 90, 0,0}, -- bottom { 0, 90,0}} -- left --</not from DrawRotatingCube, but the following lot is> -- rotation angles, 0..359, on a timer atom rx = 45, -- initially makes cube like a H ry = 35, -- " " " italic H rz = 0 constant naxes = {{Y,Z}, -- (rotate about the X-axis) {X,Z}, -- (rotate about the Y-axis) {X,Y}} -- (rotate about the Z-axis) function rotate(sequence points, atom angle, integer axis) -- -- rotate points by the specified angle about the given axis -- atom radians = angle*CD_DEG2RAD, sin_t = sin(radians), cos_t = cos(radians) integer {nx,ny} = naxes[axis] for i=1 to length(points) do atom x = points[i][nx], y = points[i][ny] points[i][nx] = x*cos_t - y*sin_t points[i][ny] = y*cos_t + x*sin_t end for return points end function function projection(sequence points, atom d) -- -- project points from {0,0,d} onto the perpendicular plane through {0,0,0} -- for i=1 to length(points) do atom {x,y,z} = points[i], denom = (1-z/d) points[i][X] = x/denom points[i][Y] = y/denom end for return points end function function nearest(sequence points) -- -- return the index of the nearest point (highest z value) -- return largest(vslice(points,Z),true) end function -- (this has has a scale added, which DrawRotatingCube.exw does not, -- plus the drawing of the digits on the faces is also new here.) procedure draw_cube(atom cx, cy, scale, integer digit) -- {cx,cy} is the centre point of the canvas -- A scale of 100 should draw ~2.5"/6cm cube on a 480x480 canvas -- digit can (now in fact) be 0..15, 0 draws a 1..6 standard dice sequence points = sq_mul(corners,scale/100) points = rotate(points,rx,X) points = rotate(points,ry,Y) points = rotate(points,rz,Z) points = projection(points,1000) integer np = nearest(points) -- -- find the three faces that contain the nearest point, -- then for each of those faces let diag be the point -- that is diagonally opposite said nearest point, and -- order by/draw those faces furthest diag away first. -- sequence faceset = {} for i=1 to length(faces) do sequence fi = faces[i] integer k = find(np,fi) -- k:=2..5, or 0 if k then integer diag = mod(k,4)+2 -- {2,3,4,5} --> {4,5,2,3} -- aka swap 2<=>4 & 3<=>5 diag = fi[diag] -- 1..8, diagonally opp. np faceset = append(faceset,{points[diag][Z],i}) end if end for faceset = sort(faceset) for i=1 to length(faceset) do integer fn = faceset[i][2] sequence face = faces[fn] integer clr = iff(digit?colours[digit]:face[1]) cdCanvasSetForeground(cd_canvas,clr) -- first fill sides (with bresenham edges), then -- redraw edges, but anti-aliased aka smoother sequence modes = {CD_FILL,CD_CLOSED_LINES} for m=1 to length(modes) do cdCanvasBegin(cd_canvas,modes[m]) for fdx=2 to 5 do sequence pt = points[face[fdx]] cdCanvasVertex(cd_canvas,cx+pt[X],cy-pt[Y]) end for cdCanvasEnd(cd_canvas) cdCanvasSetForeground(cd_canvas,CD_BLACK) end for -- </DrawRotatingCube.exw> -- now draw the number(s) on the face integer d = iff(digit?digit:fn), skip_point = 0 sequence dp, d2 if d<=9 then dp = digits[d+1] else dp = deep_copy(digits[2]) -- (ie a '1') d2 = deep_copy(digits[remainder(d,10)+1]) skip_point = length(dp)+1 atom dx = iff(d=11?1.5:2.5)*k, x2 = 1.5*k for l=1 to length(dp) do dp[l][X] += dx end for for l=1 to length(d2) do d2[l][X] -= x2 end for dp &= d2 end if dp = sq_mul(dp,scale/100) -- rotate the digit(s) onto the required face dp = rotate(dp,irot[fn][X],X) dp = rotate(dp,irot[fn][Y],Y) -- then rotate to match the cube dp = rotate(dp,rx,X) dp = rotate(dp,ry,Y) dp = rotate(dp,rz,Z) dp = projection(dp,1000) atom {x1,y1} = dp[1] for l=2 to length(dp) do atom {x2,y2} = dp[l] if l!=skip_point then cdCanvasLine(cd_canvas,cx+x1,cy-y1,cx+x2,cy-y2) end if {x1,y1} = {x2,y2} end for end for end procedure -- <copied from demo\pGUI\r3d.exw>: constant num = 90, -- number of lines dist = 11000, eye = 1450, dz = 430 sequence stars = repeat({0,0,eye+1},num) procedure draw_stars(integer w, h) atom w2 = w/2, h2 = h/2 for i=1 to num do atom {x,y,z} = stars[i], px = (eye/z*x)+w2, py = (eye/z*y)+h2, px2 = (eye/(z+250)*x)+w2, py2 = (eye/(z+250)*y)+h2 if z<eye or abs(px)>w or abs(py)>h then stars[i] = {(rnd()-0.5)*w*2,(rnd()-0.5)*h*2,rand(dist)} else stars[i][Z] = z-dz cdCanvasSetForeground(cd_canvas,iff(odd(i)?CD_WHITE:CD_YELLOW)) cdCanvasLine(cd_canvas,px,py,px2,py2) end if end for end procedure --</r3d.exw> --<from demo/rosetta/Solve15puzzle_simple.exw> enum left, down, up, right -- (nb 5-move flips it, as in down===5-up, etc) integer N = 3, -- use '8' to toggle between 8 and 15 games N2, space sequence valid_moves, zero_cost, piecemasks, goal, board string moves = "" -- Based on demo/rosetta/Solve15puzzle_simple.exw, but -- made re-entrant iterative rather than recursive, -- and able to solve either an 8 or 15 puzzle, and to -- use an entirely private copy of the board, etc. enum RESET, THINKING, SOLVED -- (solve_state values) integer solve_state = RESET, iscount = 0 enum MOVE=1, /*SKIP=2, SPACE=3, NFREE=4,*/ UNDO=5 -- (solve_stack entries) sequence solve_stack, solve_board string solve_time atom solve_t0 procedure iterative_solve() if solve_state=RESET then solve_state = THINKING -- -- The following is equivalent to: -- move := left-1 (aka no move) -- skip_move := 0 (aka no skip) -- idle_space := space -- nfree := 0 -- undo := false -- solve_stack = {{0,0,space,0,false}} solve_board = deep_copy(board) solve_t0 = time() iscount = 0 end if integer nleft = iterations_per_timeslice, new_space -- (scratch) while nleft>0 do integer {move,skip_move,idle_space,nfree,undo} = solve_stack[$] if undo then new_space = valid_moves[idle_space][move] solve_board[new_space] = solve_board[idle_space] solve_board[idle_space] = 0 else assert(solve_board[idle_space]=0) end if move += 1 if move>right then if length(solve_stack)=1 then -- (as per RESET above) assert(idle_space=space) nfree += 1 solve_stack = {{0,0,idle_space,nfree,false}} else solve_stack = solve_stack[1..$-1] -- (pop) end if else solve_stack[$][MOVE] = move solve_stack[$][UNDO] = false if move!=skip_move then new_space = valid_moves[idle_space][move] if new_space then integer piece = solve_board[new_space], zcsmv = zero_cost[idle_space][move], pmask = piecemasks[piece], zcost = (and_bits(zcsmv,pmask)=0) -- (0==free, 1==not) nfree -= zcost if nfree>=0 then solve_stack[$][UNDO] = true -- (set the undo flag) solve_board[idle_space] = piece solve_board[new_space] = 0 if idle_space=piece and solve_board=goal then moves = repeat(' ',length(solve_stack)) for i=1 to length(solve_stack) do move = solve_stack[i][MOVE] moves[i] = "ludr"[move] end for solve_state = SOLVED exit end if iscount += 1 solve_stack = append(solve_stack,{0,5-move,new_space,nfree,false}) end if end if end if end if nleft -= 1 end while solve_time = sprintf("(%s)",{elapsed(time()-solve_t0)}) end procedure function set_title() if board=goal then moves = "" return sprintf("%s - solved",{title}) elsif solve_state<=THINKING then return sprintf("%s - thinking (%,d tries)",{title,iscount}) else return sprintf("%s - solvable in %d moves %s",{title,length(moves),solve_time}) end if end function procedure move(integer d) if d then integer new_space = valid_moves[space][d] if new_space then if length(moves) and moves[1]="ludr"[d] then moves = moves[2..$] solve_time = "" else solve_state = RESET moves = "" -- (force re-examination) end if board[space] = board[new_space] board[new_space] = 0 space = new_space end if end if end procedure procedure scramble() for i=1 to 5000 do move(rand(4)) end for solve_state = RESET end procedure sequence cubies, rxyz procedure reset() N2 = N*N valid_moves = repeat(repeat(0,4),N2) zero_cost = repeat(repeat(0,4),N2) piecemasks = sq_power(2,tagset(N2-2,0)) for square=1 to N2 do integer s_row = floor((square+N-1)/N), s_col = remainder((square-1),N)+1 for move=left to right do -- (via up/down) if (move=left and s_col>1) or (move=down and s_row>1) or (move=up and s_row<N) or (move=right and s_col<N) then integer origin = square+{-1,-N,+N,+1}[move], o_row = floor((origin+N-1)/N), o_col = remainder((origin-1),N)+1 valid_moves[square][move] = origin for piece=1 to N2-1 do -- (aka target) integer t_row = floor((piece+N-1)/N), t_col = remainder((piece-1),N)+1, p_md = abs(t_row-o_row)+abs(t_col-o_col), n_md = abs(t_row-s_row)+abs(t_col-s_col) if n_md<=p_md then zero_cost[square][move] += piecemasks[piece] end if end for end if end for end for goal = tagset(N2-1)&0 board = deep_copy(goal) space = find(0,board) moves = "" -- </Solve15puzzle_simple.exw> cubies = iff(N=3?{{-1,-1},{ 0,-1},{+1,-1}, {-1, 0},{ 0, 0},{+1, 0}, {-1,+1},{ 0,+1},{+1,+1}} :iff(N=4?{{-1.5,-1.5},{-0.5,-1.5},{0.5,-1.5},{1.5,-1.5}, {-1.5,-0.5},{-0.5,-0.5},{0.5,-0.5},{1.5,-0.5}, {-1.5,+0.5},{-0.5,+0.5},{0.5,+0.5},{1.5,+0.5}, {-1.5,+1.5},{-0.5,+1.5},{0.5,+1.5},{1.5,+1.5}} :9/0)) scramble() rxyz = sq_rand(repeat({45,35,1,3},N2)) end procedure function canvas_action_cb(Ihandle canvas) cdCanvasActivate(cd_canvas) cdCanvasClear(cd_canvas) integer {w, h} = IupGetIntInt(canvas, "DRAWSIZE") atom cx = w/2, cy = h/2, dxy = min(w,h)/N, size = dxy/4.5 draw_stars(w,h) for i=1 to N2 do integer bi = board[i] if bi then atom {dx,dy} = cubies[i], r {rx,ry,rz,r} = rxyz[bi] rx = mod(rx+0.25*r,360) ry = mod(ry+0.50*r,360) rz = mod(rz+0.75*r,360) rxyz[bi] = {rx,ry,rz,r} draw_cube(cx+dx*dxy,cy-dy*dxy,size,bi) end if end for cdCanvasFlush(cd_canvas) IupSetStrAttribute(dlg,"TITLE",set_title()) return IUP_DEFAULT end function function canvas_map_cb(Ihandle canvas) IupGLMakeCurrent(canvas) if platform()=JS then cd_canvas = cdCreateCanvas(CD_IUP, canvas) else atom res = IupGetDouble(NULL, "SCREENDPI")/25.4 cd_canvas = cdCreateCanvas(CD_GL, "10x10 %g", {res}) end if cdCanvasSetBackground(cd_canvas, CD_BLACK) 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(cd_canvas, "SIZE", "%dx%d %g", {canvas_width, canvas_height, res}) return IUP_DEFAULT end function function key_cb(Ihandle ih, atom c) if c=K_ESC then return IUP_CLOSE end if -- -- aside: the precise mapping of enums/K_XXX/"ludr" is semi-ad-hoc, -- aka I'm not wasting any effort enforcing strict coherence -- when a piddling tweak or two (right here) would suffice, -- aka what does right (etc) really mean anyway, given that -- for instance piece:up is the same as saying space:down. -- integer k = 0 if c='8' then -- toggle between an 8 and 15 game N = 7-N -- (3<==>4) reset() elsif lower(c)='s' then if length(moves) then k = find(moves[1],"ludr") else scramble() end if else k = find(c,{K_RIGHT,K_DOWN,K_UP,K_LEFT}) end if move(k) -- (move(0) does nowt) IupRedraw(ih) return IUP_IGNORE end function function timer_cb(Ihandln /*ih*/) if solve_state!=SOLVED then iterative_solve() end if IupRedraw(canvas) return IUP_IGNORE end function procedure main() IupOpen() reset() canvas = IupGLCanvas("RASTERSIZE=640x480") IupSetCallbacks(canvas, {"ACTION", Icallback("canvas_action_cb"), "MAP_CB", Icallback("canvas_map_cb"), "RESIZE_CB", Icallback("canvas_resize_cb"), "KEY_CB", Icallback("key_cb")}) dlg = IupDialog(canvas,`TITLE="%s"`,{title}) IupShow(dlg) IupSetAttribute(canvas, "RASTERSIZE", NULL) Ihandle hTimer = IupTimer(Icallback("timer_cb"), 30) if platform()!=JS then IupMainLoop() IupClose() end if end procedure main()
Ring
/*
## Project : 15 Puzzle Game in 3D
*/
# Load Libraries
load "gamelib.ring" # RingAllegro Library
load "opengl21lib.ring" # RingOpenGL Library
butSize = 3
texture = list(9)
cube = list(9)
rnd = list(9)
rndok = 0
for n=1 to 9
rnd[n] = 0
next
for n=1 to 9
while true
rndok = 0
ran = random(8) + 1
for nr=1 to 9
if rnd[nr] = ran
rndok = 1
ok
next
if rndok = 0
rnd[n] = ran
exit
ok
end
next
for n=1 to 9
if rnd[n] = 9
empty = n
ok
next
#==============================================================
# To Support MacOS X
al_run_main()
func al_game_start # Called by al_run_main()
main() # Now we call our main function
#==============================================================
func main
new TicTacToe3D {
start()
}
class TicTacToe3D from GameLogic
FPS = 60
TITLE = "CalmoSoft Fifteen Puzzle Game 3D"
oBackground = new GameBackground
oGameSound = new GameSound
oGameCube = new GameCube
oGameInterface = new GameInterface
func loadresources
oGameSound.loadresources()
oBackGround.loadresources()
oGameCube.loadresources()
func drawScene
oBackground.update()
oGameInterface.update(self)
func MouseClickEvent
oGameInterface.MouseClickEvent(self)
class GameInterface
func Update oGame
prepare()
cubes(oGame)
func Prepare
w = 1024 h = 768
ratio = w / h
glViewport(0, 0, w, h)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(-120,ratio,1,120)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glEnable(GL_TEXTURE_2D)
glShadeModel(GL_SMOOTH)
glClearColor(0.0, 0.0, 0.0, 0.5)
glClearDepth(1.0)
glEnable(GL_DEPTH_TEST)
glEnable(GL_CULL_FACE)
glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
func Cubes oGame
oGame.oGameCube {
aGameMap = oGame.aGameMap
cube[1] = cube( 5 , -3 , -5 , texture[rnd[1]] )
cube[2] = cube( 0 , -3 , -5 , texture[rnd[2]] )
cube[3] = cube( -5 , -3 , -5 , texture[rnd[3]] )
cube[4] = cube( 5 , 1 , -5 , texture[rnd[4]] )
cube[5] = cube( 0 , 1 , -5 , texture[rnd[5]] )
cube[6] = cube( -5 , 1 , -5 , texture[rnd[6]] )
cube[7] = cube( 5 , 5 , -5 , texture[rnd[7]] )
cube[8] = cube( 0 , 5 , -5 , texture[rnd[8]] )
cube[9] = cube( -5 , 5 , -5 , texture[rnd[9]] )
rotate()
}
func MouseClickEvent oGame
oGame {
aBtn = Point2Button(Mouse_X,Mouse_Y)
move = 0
nRow = aBtn[1]
nCol = aBtn[2]
tile = (nRow-1)*3 + nCol
up = (empty = (tile - butSize))
down = (empty = (tile + butSize))
left = ((empty = (tile- 1)) and ((tile % butSize) != 1))
right = ((empty = (tile + 1)) and ((tile % butSize) != 0))
move = up or down or left or right
if move = 1
temp = rnd[empty]
rnd[empty] = rnd[tile]
rnd[tile] = temp
empty = tile
oGame.oGameCube {
aGameMap = oGame.aGameMap
cube[1] = cube( 5 , -3 , -5 , texture[rnd[1]] )
cube[2] = cube( 0 , -3 , -5 , texture[rnd[2]] )
cube[3] = cube( -5 , -3 , -5 , texture[rnd[3]] )
cube[4] = cube( 5 , 1 , -5 , texture[rnd[4]] )
cube[5] = cube( 0 , 1 , -5 , texture[rnd[5]] )
cube[6] = cube( -5 , 1 , -5 , texture[rnd[6]] )
cube[7] = cube( 5 , 5 , -5 , texture[rnd[7]] )
cube[8] = cube( 0 , 5 , -5 , texture[rnd[8]] )
cube[9] = cube( -5 , 5 , -5 , texture[rnd[9]] )
rotate()
}
ok
}
Class GameLogic from GraphicsAppBase
aGameMap = [
[ :n , :n , :n ] ,
[ :n , :n , :n ] ,
[ :n , :n , :n ]
]
aGameButtons = [ # x1,y1,x2,y2
[176,88,375,261], # [1,1]
[423,88,591,261], # [1,2]
[645,88,876,261], # [1,3]
[176,282,375,428], # [2,1]
[423,282,591,428], # [2,2]
[645,282,876,428], # [2,3]
[176,454,375,678], # [3,1]
[423,454,591,678], # [3,2]
[645,454,876,678] # [3,3]
]
cActivePlayer = :x
func point2button x,y
nRow = 0
nCol = 0
for t = 1 to len(aGameButtons)
rect = aGameButtons[t]
if x >= rect[1] and x <= rect[3] and
y >= rect[2] and y <= rect[4]
switch t
on 1 nRow = 1 nCol = 1
on 2 nRow = 1 nCol = 2
on 3 nRow = 1 nCol = 3
on 4 nRow = 2 nCol = 1
on 5 nRow = 2 nCol = 2
on 6 nRow = 2 nCol = 3
on 7 nRow = 3 nCol = 1
on 8 nRow = 3 nCol = 2
on 9 nRow = 3 nCol = 3
off
exit
ok
next
return [nRow,nCol]
class GameCube
bitmap bitmap2 bitmap3
textureX textureO textureN
xrot = 0.0
yrot = 0.0
zrot = 0.0
func loadresources
bitmp1 = al_load_bitmap("image/n1.jpg")
texture[1] = al_get_opengl_texture(bitmp1)
bitmp2 = al_load_bitmap("image/n2.jpg")
texture[2] = al_get_opengl_texture(bitmp2)
bitmp3 = al_load_bitmap("image/n3.jpg")
texture[3] = al_get_opengl_texture(bitmp3)
bitmp4 = al_load_bitmap("image/n4.jpg")
texture[4] = al_get_opengl_texture(bitmp4)
bitmp5 = al_load_bitmap("image/n5.jpg")
texture[5] = al_get_opengl_texture(bitmp5)
bitmp6 = al_load_bitmap("image/n6.jpg")
texture[6] = al_get_opengl_texture(bitmp6)
bitmp7 = al_load_bitmap("image/n7.jpg")
texture[7] = al_get_opengl_texture(bitmp7)
bitmp8 = al_load_bitmap("image/n8.jpg")
texture[8] = al_get_opengl_texture(bitmp8)
bitmp9 = al_load_bitmap("image/empty.png")
texture[9] = al_get_opengl_texture(bitmp9)
func cube(x,y,z,nTexture)
glLoadIdentity()
glTranslatef(x,y,z)
glRotatef(xrot,1.0,0.0,0.0)
glRotatef(yrot,0.0,1.0,0.0)
glRotatef(zrot,0.0,0.0,1.0)
setCubeTexture(nTexture)
drawCube()
func setCubeTexture cTexture
glBindTexture(GL_TEXTURE_2D, cTexture)
func Rotate
xrot += 0.3 * 5
yrot += 0.2 * 5
zrot += 0.4 * 5
func drawcube
glBegin(GL_QUADS)
// Front Face
glTexCoord2f(0.0, 0.0) glVertex3f(-1.0, -1.0, 1.0)
glTexCoord2f(1.0, 0.0) glVertex3f( 1.0, -1.0, 1.0)
glTexCoord2f(1.0, 1.0) glVertex3f( 1.0, 1.0, 1.0)
glTexCoord2f(0.0, 1.0) glVertex3f(-1.0, 1.0, 1.0)
// Back Face
glTexCoord2f(1.0, 0.0) glVertex3f(-1.0, -1.0, -1.0)
glTexCoord2f(1.0, 1.0) glVertex3f(-1.0, 1.0, -1.0)
glTexCoord2f(0.0, 1.0) glVertex3f( 1.0, 1.0, -1.0)
glTexCoord2f(0.0, 0.0) glVertex3f( 1.0, -1.0, -1.0)
// Top Face
glTexCoord2f(0.0, 1.0) glVertex3f(-1.0, 1.0, -1.0)
glTexCoord2f(0.0, 0.0) glVertex3f(-1.0, 1.0, 1.0)
glTexCoord2f(1.0, 0.0) glVertex3f( 1.0, 1.0, 1.0)
glTexCoord2f(1.0, 1.0) glVertex3f( 1.0, 1.0, -1.0)
// Bottom Face
glTexCoord2f(1.0, 1.0) glVertex3f(-1.0, -1.0, -1.0)
glTexCoord2f(0.0, 1.0) glVertex3f( 1.0, -1.0, -1.0)
glTexCoord2f(0.0, 0.0) glVertex3f( 1.0, -1.0, 1.0)
glTexCoord2f(1.0, 0.0) glVertex3f(-1.0, -1.0, 1.0)
// Right face
glTexCoord2f(1.0, 0.0) glVertex3f( 1.0, -1.0, -1.0)
glTexCoord2f(1.0, 1.0) glVertex3f( 1.0, 1.0, -1.0)
glTexCoord2f(0.0, 1.0) glVertex3f( 1.0, 1.0, 1.0)
glTexCoord2f(0.0, 0.0) glVertex3f( 1.0, -1.0, 1.0)
// Left Face
glTexCoord2f(0.0, 0.0) glVertex3f(-1.0, -1.0, -1.0)
glTexCoord2f(1.0, 0.0) glVertex3f(-1.0, -1.0, 1.0)
glTexCoord2f(1.0, 1.0) glVertex3f(-1.0, 1.0, 1.0)
glTexCoord2f(0.0, 1.0) glVertex3f(-1.0, 1.0, -1.0)
glEnd()
class GameBackground
nBackX = 0
nBackY = 0
nBackDiffx = -1
nBackDiffy = -1
nBackMotion = 1
aBackMotionList = [
[ -1, -1 ] , # Down - Right
[ 0 , 1 ] , # Up
[ -1, -1 ] , # Down - Right
[ 0 , 1 ] , # Up
[ 1 , -1 ] , # Down - Left
[ 0 , 1 ] , # Up
[ 1 , -1 ] , # Down - Left
[ 0 , 1 ] # Up
]
bitmap
func Update
draw()
motion()
func draw
al_draw_bitmap(bitmap,nBackX,nBackY,1)
func motion
nBackX += nBackDiffx
nBackY += nBackDiffy
if (nBackY = -350) or (nBackY = 0)
nBackMotion++
if nBackMotion > len(aBackMotionList)
nBackMotion = 1
ok
nBackDiffx = aBackMotionList[nBackMotion][1]
nBackDiffy = aBackMotionList[nBackMotion][2]
ok
func loadResources
bitmap = al_load_bitmap("image/back.jpg")
class GameSound
sample sampleid
func loadresources
sample = al_load_sample( "sound/music1.wav" )
sampleid = al_new_allegro_sample_id()
al_play_sample(sample, 1.0, 0.0,1.0,ALLEGRO_PLAYMODE_LOOP,sampleid)
class GraphicsAppBase
display event_queue ev timeout
timer
redraw = true
FPS = 60
SCREEN_W = 1024
SCREEN_H = 700
KEY_UP = 1
KEY_DOWN = 2
KEY_LEFT = 3
KEY_RIGHT = 4
Key = [false,false,false,false]
Mouse_X = 0
Mouse_Y = 0
TITLE = "Graphics Application"
PRINT_MOUSE_XY = False
func start
SetUp()
loadResources()
eventsLoop()
destroy()
func setup
al_init()
al_init_font_addon()
al_init_ttf_addon()
al_init_image_addon()
al_install_audio()
al_init_acodec_addon()
al_reserve_samples(1)
al_set_new_display_flags(ALLEGRO_OPENGL)
display = al_create_display(SCREEN_W,SCREEN_H)
al_set_window_title(display,TITLE)
al_clear_to_color(al_map_rgb(0,0,0))
event_queue = al_create_event_queue()
al_register_event_source(event_queue,
al_get_display_event_source(display))
ev = al_new_allegro_event()
timeout = al_new_allegro_timeout()
al_init_timeout(timeout, 0.06)
timer = al_create_timer(1.0 / FPS)
al_register_event_source(event_queue,
al_get_timer_event_source(timer))
al_start_timer(timer)
al_install_mouse()
al_register_event_source(event_queue,
al_get_mouse_event_source())
al_install_keyboard()
al_register_event_source(event_queue,
al_get_keyboard_event_source())
func eventsLoop
while true
al_wait_for_event_until(event_queue, ev, timeout)
switch al_get_allegro_event_type(ev)
on ALLEGRO_EVENT_DISPLAY_CLOSE
CloseEvent()
on ALLEGRO_EVENT_TIMER
redraw = true
on ALLEGRO_EVENT_MOUSE_AXES
mouse_x = al_get_allegro_event_mouse_x(ev)
mouse_y = al_get_allegro_event_mouse_y(ev)
if PRINT_MOUSE_XY
see "x = " + mouse_x + nl
see "y = " + mouse_y + nl
ok
on ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY
mouse_x = al_get_allegro_event_mouse_x(ev)
mouse_y = al_get_allegro_event_mouse_y(ev)
on ALLEGRO_EVENT_MOUSE_BUTTON_UP
MouseClickEvent()
on ALLEGRO_EVENT_KEY_DOWN
switch al_get_allegro_event_keyboard_keycode(ev)
on ALLEGRO_KEY_UP
key[KEY_UP] = true
on ALLEGRO_KEY_DOWN
key[KEY_DOWN] = true
on ALLEGRO_KEY_LEFT
key[KEY_LEFT] = true
on ALLEGRO_KEY_RIGHT
key[KEY_RIGHT] = true
off
on ALLEGRO_EVENT_KEY_UP
switch al_get_allegro_event_keyboard_keycode(ev)
on ALLEGRO_KEY_UP
key[KEY_UP] = false
on ALLEGRO_KEY_DOWN
key[KEY_DOWN] = false
on ALLEGRO_KEY_LEFT
key[KEY_LEFT] = false
on ALLEGRO_KEY_RIGHT
key[KEY_RIGHT] = false
on ALLEGRO_KEY_ESCAPE
exit
off
off
if redraw and al_is_event_queue_empty(event_queue)
redraw = false
drawScene()
al_flip_display()
ok
callgc()
end
func destroy
al_destroy_timer(timer)
al_destroy_allegro_event(ev)
al_destroy_allegro_timeout(timeout)
al_destroy_event_queue(event_queue)
al_destroy_display(display)
al_exit()
func loadresources
func drawScene
func MouseClickEvent
exit # Exit from the Events Loop
func CloseEvent
exit # Exit from the Events Loop
Wren
The palette is somewhat different from the Go example and, if the player is eventually successful in assembling the cubes in order, the white cube changes to gold and displays the number 16.
import "dome" for Window
import "graphics" for Canvas, Color, Font
import "input" for Keyboard
import "random" for Random
import "math" for Math
import "./polygon" for Polygon
var Palette = [
Color.purple,
Color.darkblue,
Color.darkpurple,
Color.darkgreen,
Color.brown,
Color.darkgray,
Color.lightgray,
Color.red,
Color.orange,
Color.yellow,
Color.green,
Color.blue,
Color.indigo,
Color.pink,
Color.peach,
Color.white
]
var Rand = Random.new()
class FifteenPuzzle3d {
construct new() {
Window.resize(960, 840)
Canvas.resize(960, 840)
Window.title = "15-puzzle game using 3D cubes"
_radius = Canvas.height / 14
// see Go-fonts page
Font.load("Go-Regular20", "Go-Regular.ttf", 20)
Canvas.font = "Go-Regular20"
_blank = 15
_moves = 0
_gameOver = false
}
init() {
_centers = List.filled(16, null)
_cubes = List.filled(15, 0)
for (i in 0..14) _cubes[i] = i
Rand.shuffle(_cubes)
_cubes.add(15)
var x = Canvas.width / 10
var y = _radius
for (i in 0..3) {
var cx = 2 * x * (i + 1)
for (j in 0..3) {
var cy = (x + y) * (j + 1)
_centers[j * 4 + i] = [cx, cy]
}
}
for (i in 0..15) drawCube(_cubes[i], i)
}
drawCube(n, pos) {
var cx = _centers[pos][0]
var cy = _centers[pos][1]
var angle = Num.pi / 6
var incr = 2 * angle
var hexagon = Polygon.regular(6, cx, cy, _radius, 90)
hexagon.drawfill(Palette[n])
for (i in [1, 3, 5]) {
var vx = (_radius * Math.cos(angle + i*incr) + cx).floor
var vy = (_radius * Math.sin(angle + i*incr) + cy).floor
Canvas.line(cx, cy, vx, vy, Color.black)
}
var ns = (n < 15 || _gameOver) ? (n + 1).toString : ""
var hr = _radius * 0.5
var er = _radius * 0.125
var tqr = _radius * 0.75
Canvas.print(ns, cx + hr - er, cy, Color.white)
Canvas.print(ns, cx - hr - er, cy, Color.white)
Canvas.print(ns, cx - er, cy - tqr, Color.white)
}
completed {
for (i in 0..15) {
if (_cubes[i] != i) return false
}
Palette[15] = Color.rgb(255, 215, 0) // gold
return true
}
update() {
var changed = false
if (Keyboard["Left"].justPressed) {
if (_blank%4 != 0) {
_cubes.swap(_blank, _blank-1)
_blank = _blank - 1
_moves =_moves + 1
changed = true
}
} else if (Keyboard["Right"].justPressed) {
if ((_blank+1)%4 != 0) {
_cubes.swap(_blank, _blank+1)
_blank = _blank + 1
_moves = _moves + 1
changed = true
}
} else if (Keyboard["Up"].justPressed) {
if (_blank > 3) {
_cubes.swap(_blank, _blank-4)
_blank = _blank - 4
_moves = _moves + 1
changed = true
}
} else if (Keyboard["Down"].justPressed) {
if (_blank < 12) {
_cubes.swap(_blank, _blank+4)
_blank = _blank + 4
_moves = _moves + 1
changed = true
}
}
if (changed) {
Canvas.cls(Color.black)
_gameOver = completed
for (i in 0..15) drawCube(_cubes[i], i)
if (!_gameOver) {
var m = "Moves = %(_moves)"
Canvas.print(m, 0.4 * Canvas.width, 13 * _radius, Color.white)
} else {
var m = "You've completed the puzzle in %(_moves) moves!"
Canvas.print(m, 0.3 * Canvas.width, 13 * _radius, Color.white)
}
}
}
draw(alpha) {}
}
var Game = FifteenPuzzle3d.new()