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. <lang go>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()
}</lang>
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
<lang 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 </lang>
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. <lang ecmascript>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()</lang>