I've made a litte puzzle game, which I wanted to share with you guys. I challenged myself to get something done during Global GameJam, in less than 500 Lines of Code. In the end I got a lot of lines left so I've put all sorts of fancy stuff in it, like a difficulty selection menu, and animated tiles.
I'm posting the barebones code here (~480 lines total), so you can copy/paste it and try it for yourself. I hope that's not to big?
If you want to have the full experience with sounds, I urge you to download the current version, though. I've linked it farther down this thread (source code is still included):
- You need to provide a .jpg image for the program to reckognize. Any .jpg image will do, the program will adjust it to a proper size and keep the aspect ratio intact (FileRequester() will ask you to pick a .jpg)
- Select a difficulty
- Mind that, depending on measurements of your image, there might be a fixed border layout. The interactive puzzle part will be located in it's center.
- Right clicking a tile will rotate it by 90° clockwise
- Left-Clicking a tile tags/untags it.
- As soon as there are 2 tiles tagged, they will swap positions.
- The program will reckognize, once the puzzle has been completed successfully.
- Press ESC or close the window to exit the game.
Now have fun, and thanks for having me!
Code: Select all
EnableExplicit
UseJPEGImageDecoder()
Declare selectDifficulty()
Declare ini()
Declare aspectResize(picID,newX,newY)
Declare loop()
Declare processInput()
Declare processTimer()
Declare checkPuzzle()
Declare checkTile(*pointer)
Declare clearTags()
Declare yay()
Declare inRange(value,min,max)
Structure TILE
index.i
trueX.i
trueY.i
x.i
y.i
xMicro.i
yMicro.i
xMicroDir.i
yMicroDir.i
rotation.i
tagged.i
EndStructure
Global NewList tile.TILE()
#xRes = 1024
#yRes = 768
Global screen
Global pic
Global difficulty
Global selectSPR
Global selectX,selectY
Global marginSPR
Global Dim tileSPR(0)
Global tagSPR
Global frameSPR
Global xMax, yMax
Global xOffset, yOffset
Global xMargin, yMargin
Global tileSize
Global autoSnap
Global swapping
Global targetAngle
Global gameOver
Global Dim *tag.TILE(1)
Global frameDuration, frameFinished
Global perSecond.f
Enumeration
#nullTag
#tagged
#locked
EndEnumeration
ini()
loop()
End
Procedure selectDifficulty()
Define event
Define diffSelection
diffSelection = OpenWindow(#PB_Any, 0, 0, 160, 310, "Select Difficulty", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ButtonGadget(2,30,20,100,20,"Why even play")
ButtonGadget(3,30,45,100,20,"I hate Puzzles")
ButtonGadget(4,30,70,100,20,"Easy")
ButtonGadget(5,30,95,100,20,"Medium")
ButtonGadget(6,30,120,100,20,"Harder")
ButtonGadget(7,30,145,100,20,"Challenge me")
ButtonGadget(8,30,170,100,20,"Master")
ButtonGadget(9,30,195,100,20,"This is work")
ButtonGadget(10,30,220,100,20,"Masochistic")
ButtonGadget(11,30,245,100,20,"Impossible")
CheckBoxGadget(12,30,280,100,20,"AutoSnap",#PB_CheckBox_Center)
SetGadgetState(12,#True)
SetActiveWindow(diffSelection)
Repeat
event = WaitWindowEvent(16)
difficulty = EventGadget()
Until inRange(difficulty,2,11) Or event = #PB_Event_CloseWindow
autoSnap = GetGadgetState(12)
CloseWindow(diffSelection)
If event = #PB_Event_CloseWindow
End
EndIf
EndProcedure
Procedure ini()
Define fileName$
Define x, y
Define xSize
Define ySize
Define factor.f
Define newX, newY
Define defSide
Define count
Dim puzzle(0,0)
fileName$ = OpenFileRequester("Choose any JPG", "", "JPG|*.jpg", 0)
If fileName$ = ""
End
EndIf
selectDifficulty()
If InitSprite()
screen = OpenWindow(#PB_Any,0,0,#xRes,#yRes,"PurePuzzle (©diceman, 2018) v1.13",#PB_Window_ScreenCentered | #PB_Window_SystemMenu)
If OpenWindowedScreen(WindowID(screen),0,0,#xRes,#yRes)
InitKeyboard()
InitMouse()
EndIf
EndIf
;Bild laden und Größe anpassen
pic = LoadImage(#PB_Any,fileName$)
xSize = ImageWidth(pic)
ySize = ImageHeight(pic)
newX = #xRes
factor = ySize/xSize
newY = newX*factor
If newY > #yRes
newX = 0
newY = #yRes
Else
newY = 0
EndIf
aspectResize(pic,newX,newY)
;Anzahl der Teile bestimmen
xSize = ImageWidth(pic)
ySize = ImageHeight(pic)
If xSize > ySize
defSide = ySize
Else
defSide = xSize
EndIf
tileSize = defSide/difficulty
xMax = (xSize/tileSize)-1
yMax = (ySize/tileSize)-1
;Bild im Hintergrund zeichnen für weitere Operationen
If StartDrawing(ScreenOutput())
DrawImage(ImageID(pic),0,0)
StopDrawing()
EndIf
;Rahmen erkennen
xMargin = (xSize - ((xMax+1)*tileSize))/2
yMargin = (ySize - ((yMax+1)*tileSize))/2
xOffset = (#xRes-xSize)/2
yOffset = (#yRes-ySize)/2
;Teile als Sprites speichern
count = -1
For x = 0 To xMax
For y = 0 To yMax
count +1
ReDim tileSPR(count)
tileSPR(count) = GrabSprite(#PB_Any,(x*tileSize)+xMargin,(y*tileSize)+yMargin,tileSize,tileSize)
AddElement(tile())
tile()\index = count
tile()\trueX = x
tile()\trueY = y
Next
Next
;Rahmen speichern
If StartDrawing(ScreenOutput())
DrawingMode(#PB_2DDrawing_Default)
Box(xMargin,yMargin,(xMax+1)*tileSize,(yMax+1)*tileSize,RGB(0,0,0))
StopDrawing()
EndIf
GrabSprite(marginSPR,0,0,xSize,ySize) ;Der Einfachheit halber wird das komplette Bild als Rahmen gespeichert und als unterste Ebene gezeichnet. Die Puzzleteile liegen darüber.
;Puzzle mischen
Dim puzzle(xMax,yMax)
ForEach tile()
Repeat
tile()\x = Random(xMax)
tile()\y = Random(yMax)
tile()\rotation = Random(3)*90
Until puzzle(tile()\x,tile()\y) = 0
puzzle(tile()\x,tile()\y) = #True
Next
;übrige Sprites initialisieren
tagSPR = CreateSprite(#PB_Any,tileSize,tileSize)
If StartDrawing(SpriteOutput(tagSPR))
DrawingMode(#PB_2DDrawing_Outlined)
Box(0,0,tileSize,tileSize,RGB(255,0,0))
Box(1,1,tileSize-2,tileSize-2,RGB(255,0,0))
Box(3,3,tileSize-6,tileSize-6,RGB(255,0,0))
StopDrawing()
EndIf
selectSPR = CreateSprite(#PB_Any,tileSize,tileSize)
TransparentSpriteColor(selectSPR,RGB(255,0,0))
frameSPR = CreateSprite(#PB_Any,tileSize,tileSize)
If StartDrawing(SpriteOutput(frameSPR))
DrawingMode(#PB_2DDrawing_Outlined)
Box(0,0,tileSize,tileSize,RGB(255,255,255))
StopDrawing()
EndIf
EndProcedure
Procedure processInput()
Define *pointer.TILE
Define x, y
Define count
Define event
Define absCount
selectX = -1
selectY = -1
ForEach tile()
If tile()\tagged <> #locked
If inRange(WindowMouseX(screen), xOffset+xMargin+(tile()\x*tileSize)+tile()\xMicro, xOffset+xMargin+((tile()\x+1)*tileSize)+tile()\xMicro) And inRange(WindowMouseY(screen), yOffset+yMargin+(tile()\y*tileSize)+tile()\yMicro,yOffset+yMargin+((tile()\y+1)*tileSize)+tile()\yMicro)
*pointer = @tile()
selectX = *pointer\x
selectY = *pointer\y
Break
EndIf
EndIf
Next
event = WaitWindowEvent(16)
If swapping Or targetAngle
ProcedureReturn #False
EndIf
If event = #PB_Event_CloseWindow
gameOver = 2
EndIf
If *pointer
If event = #PB_Event_LeftClick
Select *pointer\tagged
Case #tagged
*pointer\tagged = #nullTag
*tag(0) = #Null
Case #nullTag
*pointer\tagged = #tagged
If *tag(0)
count = 1
EndIf
*tag(count) = *pointer
checkTile(*pointer)
EndSelect
If *tag(0) And *tag(1)
swapping = #True
For count = 0 To 1
ChangeCurrentElement(tile(),*tag(count))
MoveElement(tile(),#PB_List_Last)
absCount = Abs(count-1)
If *tag(count)\x > *tag(absCount)\x
*tag(count)\xMicroDir = -1
EndIf
If *tag(count)\x < *tag(absCount)\x
*tag(count)\xMicroDir = 1
EndIf
If *tag(count)\y > *tag(absCount)\y
*tag(count)\yMicroDir = -1
EndIf
If *tag(count)\y < *tag(absCount)\y
*tag(count)\yMicroDir = 1
EndIf
Next
EndIf
EndIf
If event = #PB_Event_RightClick
clearTags()
*tag(0) = *pointer
ChangeCurrentElement(tile(),*tag(0))
MoveElement(tile(),#PB_List_Last)
targetAngle = *tag(0)\rotation +90
EndIf
EndIf
EndProcedure
Procedure processTimer()
Define count
Define absCount
If swapping
For count = 0 To 1
absCount = Abs(count-1)
*tag(count)\xMicro + (*tag(count)\xMicroDir*Abs((*tag(count)\x*tileSize)-(*tag(absCount)\x*tileSize))*perSecond*4)
*tag(count)\yMicro + (*tag(count)\yMicroDir*Abs((*tag(count)\y*tileSize)-(*tag(absCount)\y*tileSize))*perSecond*4)
If *tag(count)\xMicroDir = -1 And (*tag(count)\x*tileSize)+*tag(count)\xMicro <= *tag(absCount)\x*tileSize
*tag(count)\xMicroDir = 0
EndIf
If *tag(count)\xMicroDir = 1 And (*tag(count)\x*tileSize)+*tag(count)\xMicro >= *tag(absCount)\x*tileSize
*tag(count)\xMicroDir = 0
EndIf
If *tag(count)\yMicroDir = -1 And (*tag(count)\y*tileSize)+*tag(count)\yMicro <= *tag(absCount)\y*tileSize
*tag(count)\yMicroDir = 0
EndIf
If *tag(count)\yMicroDir = 1 And (*tag(count)\y*tileSize)+*tag(count)\yMicro >= *tag(absCount)\y*tileSize
*tag(count)\yMicroDir = 0
EndIf
Next
If *tag(0)\xMicroDir = 0 And *tag(0)\yMicroDir = 0 And *tag(1)\xMicroDir = 0 And *tag(1)\yMicroDir = 0
For count = 0 To 1
*tag(count)\xMicro = 0
*tag(count)\yMicro = 0
Next
swapping = #False
;markierte Teile tauschen
Swap *tag(0)\x, *tag(1)\x
Swap *tag(0)\y, *tag(1)\y
checkTile(*tag(0))
checkTile(*tag(1))
clearTags()
checkPuzzle()
EndIf
EndIf
If targetAngle
*tag(0)\rotation+(1000*perSecond)
If *tag(0)\rotation >= targetAngle
*tag(0)\rotation = targetAngle
If *tag(0)\rotation = 360
*tag(0)\rotation = 0
EndIf
targetAngle = 0
checkTile(*tag(0))
clearTags()
checkPuzzle()
EndIf
EndIf
EndProcedure
Procedure clearTags()
Define count
For count = 0 To 1
If *tag(count)
*tag(count)\tagged = #nullTag
*tag(count) = #Null
EndIf
Next
EndProcedure
Procedure loop()
Define event
Define x, y
Repeat
ClearScreen(RGB(0,0,0))
DisplaySprite(marginSPR,xOffset,yOffset)
ForEach tile()
RotateSprite(tileSPR(tile()\index),tile()\rotation,#PB_Absolute)
DisplaySprite(tileSPR(tile()\index),xOffset+xMargin+(tile()\x*tileSize)+tile()\xMicro,yOffset+yMargin+(tile()\y*tileSize)+tile()\yMicro)
If autoSnap And tile()\tagged <> #locked
RotateSprite(frameSPR,tile()\rotation,#PB_Absolute)
DisplayTransparentSprite(frameSPR,xOffset+xMargin+(tile()\x*tileSize)+tile()\xMicro,yOffset+yMargin+(tile()\y*tileSize)+tile()\yMicro,90)
EndIf
If selectX = tile()\x And selectY = tile()\y
RotateSprite(selectSPR,tile()\rotation,#PB_Absolute)
DisplayTransparentSprite(selectSPR,xOffset+xMargin+(selectX*tileSize)+tile()\xMicro,yOffset+yMargin+(selectY*tileSize)+tile()\yMicro,100)
EndIf
If tile()\tagged = #tagged
DisplayTransparentSprite(tagSPR,xOffset+xMargin+(tile()\x*tileSize),yOffset+yMargin+(tile()\y*tileSize))
EndIf
Next
FlipBuffers()
ExamineKeyboard()
If Not gameOver
processInput()
processTimer()
Else
yay()
EndIf
;Delta Time
perSecond = frameDuration/1000
frameDuration = ElapsedMilliseconds() - frameFinished
frameFinished = ElapsedMilliseconds()
Until KeyboardPushed(#PB_Key_Escape) Or gameOver = 2
EndProcedure
Procedure checkPuzzle()
ForEach tile()
If tile()\x <> tile()\trueX Or tile()\y <> tile()\trueY Or tile()\rotation <> 0
ProcedureReturn #False
EndIf
Next
ForEach tile()
tile()\tagged = #locked
Next
selectX = -1
selectY = -1
gameOver = 1
EndProcedure
Procedure checkTile(*pointer.TILE)
Define count
If Not autoSnap
ProcedureReturn #False
EndIf
If *pointer\x = *pointer\trueX And *pointer\y = *pointer\trueY And *pointer\rotation = 0
*pointer\tagged = #locked
For count = 0 To 1
If *tag(count) = *pointer
*tag(count) = #Null
EndIf
Next
*pointer = #Null
EndIf
EndProcedure
Procedure yay()
Define confirm
Define event
confirm = OpenWindow(#PB_Any, 0, 0, 180, 70, "* * * * * * * * * * * * * * * * * * * * *",#PB_Window_WindowCentered)
TextGadget(0,45,10,200,20,"Congratulations!")
ButtonGadget(1,30,35,100,20,"Yay")
Repeat
SetActiveWindow(confirm)
event = WaitWindowEvent(16)
Until EventGadget() = 1 Or event = #PB_Event_CloseWindow
gameOver = 2
EndProcedure
Procedure aspectResize(picID,newX,newY)
Define x = ImageWidth(picID)
Define y = ImageHeight(picID)
Define factor.f
If newX And newY = 0
factor = y/x
newY = newX*factor
EndIf
If newX = 0 And newY
factor = x/y
newX = newY*factor
EndIf
ResizeImage(picID,newX,newY)
EndProcedure
Procedure inRange(value,min,max)
If value >= min And value <= max
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure