-
Notifications
You must be signed in to change notification settings - Fork 2
/
target9.nim
238 lines (218 loc) · 8.78 KB
/
target9.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# ****************************************************************************************
#
# naylib example - target 9 puzzle game
#
# Example originally created with naylib 5.1
#
# Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
# BSD-like license that allows static linking with closed source software
#
# Copyright (c) 2024 Antonis Geralis (@planetis-m)
#
# ****************************************************************************************
import raylib, raymath, std/[strformat, math, lenientops, random]
const
screenWidth = 800
screenHeight = 450
TilemapWidth = 348 # Width of the tilemap area
TilemapOffset = Vector2(
x: (screenWidth - TilemapWidth) div 2, # Center tilemap horizontally
y: (screenHeight - TilemapWidth) div 2 # Center tilemap vertically
)
TileSpacing = 12 # Spacing between tiles
TileCount = 3
TileWidth = (TilemapWidth - (TileCount + 1)*TileSpacing) div TileCount
# Color scheme
BackgroundColor = Color(r: 0x2c, g: 0x3e, b: 0x50, a: 255) # Dark blue background
TileNormalColor = Color(r: 0xec, g: 0xf0, b: 0xf1, a: 255) # Light gray tiles
TileHighlightColor = Color(r: 0x34, g: 0x98, b: 0xdb, a: 255) # Blue highlight
TileSelectedColor = Color(r: 0xe7, g: 0x4c, b: 0x3c, a: 255) # Red selected tile
TextNormalColor = Color(r: 0x2c, g: 0x3e, b: 0x50, a: 255) # Dark blue text
TextHighlightColor = Color(r: 0xec, g: 0xf0, b: 0xf1, a: 255) # Brighter text
TileSelectedRingColor = Color(r: 0xf3, g: 0x9c, b: 0x12, a: 255) # Brighter orange
WinColor = Color(r: 0x2e, g: 0xcc, b: 0x71, a: 255) # Green for win message
LoseColor = Color(r: 0xe7, g: 0x4c, b: 0x3c, a: 255) # Red for lose message
type
Grid = array[TileCount, array[TileCount, int]] # Type for representing the grid of tiles
Move = object
row, col: int32
GameState = enum
Playing, Won, Lost
var
grid: Grid
undoStack: seq[Move]
selectedRow, selectedCol: int32 = -1
remainingMoves: int32 # Count player moves
gameState = Playing
initialMoves: int32 = 3
proc isIntentionalSwipe(gesture: Gesture): bool =
if isGestureDetected(gesture):
let dragVec = getGestureDragVector()
result = lengthSqr(dragVec) >= 0.375
else: result = false
proc initGame() =
# Initialize the game grid
for row in 0..<TileCount:
for col in 0..<TileCount:
grid[row][col] = 9
# Randomly make initialMoves moves
for _ in 1..initialMoves:
let row = rand(0..<TileCount)
let col = rand(0..<TileCount)
# Decrement chosen tile and surrounding cells
for i in 0..<TileCount:
grid[i][col] = (grid[i][col] + 9) mod 10
for j in 0..<TileCount:
if j != col:
grid[row][j] = (grid[row][j] + 9) mod 10
# Reset game state
undoStack.setLen(0)
selectedRow = -1
selectedCol = -1
remainingMoves = initialMoves
gameState = Playing
proc checkWin(): bool =
# Check if all tiles in the grid are 9 (win condition)
for row in 0..<TileCount:
for col in 0..<TileCount:
if grid[row][col] != 9:
return false
return true
proc pushMove(stack: var seq[Move], row, col: int32) =
selectedRow = row
selectedCol = col
stack.add(Move(row: row, col: col))
proc undoMove =
if undoStack.len > 0:
let move = undoStack.pop()
selectedRow = move.row
selectedCol = move.col
# Decrement chosen tile and surrounding cells
for i in 0..<TileCount:
grid[i][move.col] = (grid[i][move.col] + 9) mod 10
for j in 0..<TileCount:
if j != move.col:
grid[move.row][j] = (grid[move.row][j] + 9) mod 10
inc remainingMoves # Update move count
proc increaseRowAndColumn(row, col: int32) =
# Increment clicked tile and surrounding cells
for i in 0..<TileCount:
grid[i][col] = (grid[i][col] + 1) mod 10
if i != col:
grid[row][i] = (grid[row][i] + 1) mod 10
proc getTileRec(row, col: int32): Rectangle =
# Calculate the rectangle for a specific tile
result = Rectangle(
x: TilemapOffset.x + (col + 1)*TileSpacing + col*TileWidth,
y: TilemapOffset.y + (row + 1)*TileSpacing + row*TileWidth,
width: TileWidth, height: TileWidth
)
proc handleInput() =
const GridRec = Rectangle(
x: TilemapOffset.x, y: TilemapOffset.y,
width: TilemapWidth, height: TilemapWidth)
# Handle mouse input and update game state accordingly
if isMouseButtonPressed(Left):
# Get mouse position
let mousePos = getMousePosition()
# Check if mouse is within tilemap bounds
if gameState == Playing and checkCollisionPointRec(mousePos, GridRec):
# Loop through each tile in the grid
block outer:
for row in 0..<TileCount:
for col in 0..<TileCount:
# Get the rectangle for the current tile
let tileRec = getTileRec(row.int32, col.int32)
# Check if mouse is within the tile rectangle
if checkCollisionPointRec(mousePos, tileRec):
# Save the current state before making a move
pushMove(undoStack, row.int32, col.int32)
increaseRowAndColumn(row.int32, col.int32)
dec remainingMoves # Update move count
break outer
# Check for undo command
if isKeyPressed(U) or isIntentionalSwipe(SwipeRight):
undoMove()
gameState = Playing
elif isKeyPressed(R) or isIntentionalSwipe(SwipeUp):
initGame()
proc drawBoxedText(text: string, rect: Rectangle, fontSize: int32, fgColor: Color) =
# Center text within a given rectangle
let font = getFontDefault()
let spacing = ceil(fontSize / 20'f32)
let textSize = measureText(font, text, fontSize.float32, spacing)
# Calculate centered position for text
let pos = Vector2(
x: rect.x + (rect.width - textSize.x) / 2,
y: rect.y + (rect.height - textSize.y) / 2
)
drawText(font, text, pos, fontSize.float32, spacing, fgColor)
proc drawTilesGrid() =
drawRectangleRounded(Rectangle(
x: TilemapOffset.x - 10, y: TilemapOffset.y - 10,
width: TilemapWidth + 20, height: TilemapWidth + 20
), 0.1, 10, BackgroundColor)
for row in 0..<TileCount:
for col in 0..<TileCount:
let tileRect = getTileRec(row.int32, col.int32)
let isSelected = row == selectedRow and col == selectedCol
let isHighlighted = row == selectedRow or col == selectedCol
let tileColor = if isSelected: TileSelectedColor
elif isHighlighted: TileHighlightColor
else: TileNormalColor
drawRectangleRounded(tileRect, 0.2, 10, tileColor)
# Draw outer ring for selected tile
if isSelected:
drawRectangleRoundedLines(tileRect, 0.2, 10, 3, TileSelectedRingColor)
let textColor = if isSelected or isHighlighted: TextHighlightColor else: TextNormalColor
drawBoxedText($grid[row][col], tileRect, 40, textColor)
proc drawGameOverMessage() =
const messageRect = Rectangle(
x: 0, y: screenHeight div 2 - 30,
width: screenWidth, height: 60)
if gameState == Won:
drawRectangle(messageRect, colorAlpha(WinColor, 0.6))
drawBoxedText("You Win!", messageRect, 40, TileNormalColor)
else:
drawRectangle(messageRect, colorAlpha(LoseColor, 0.6))
drawBoxedText("Game Over!", messageRect, 40, TileNormalColor)
# ----------------------------------------------------------------------------------------
# Program main entry point
# ----------------------------------------------------------------------------------------
proc main() =
# Initialization
# --------------------------------------------------------------------------------------
# Set up the raylib window
initWindow(screenWidth, screenHeight, "raylib example - target 9 puzzle")
try:
randomize()
# Initialize game state
initGame()
setTargetFPS(60)
# ------------------------------------------------------------------------------------
# Main game loop
while not windowShouldClose():
# Update
# ----------------------------------------------------------------------------------
handleInput() # Handle mouse input and logic
if remainingMoves <= 0:
gameState = if checkWin(): Won else: Lost
# ----------------------------------------------------------------------------------
# Draw
# ----------------------------------------------------------------------------------
beginDrawing()
clearBackground(RayWhite)
# Draw the grid and game elements
drawTilesGrid()
if gameState == Playing:
drawText(&"Remaining moves: {remainingMoves}", 10, 10, 20, DarkGray)
else:
drawGameOverMessage()
drawText("Press U to undo last move, R to start a new game.", 10, 420, 20, DarkGray)
endDrawing()
# ----------------------------------------------------------------------------------
# De-Initialization
# ------------------------------------------------------------------------------------
finally:
closeWindow() # Close window and OpenGL context
main()