Skip to content

Instantly share code, notes, and snippets.

@naranyala
Created June 20, 2025 15:10
Show Gist options
  • Save naranyala/b1ced095422ae17bf5fe7bc9e0a0f62b to your computer and use it in GitHub Desktop.
Save naranyala/b1ced095422ae17bf5fe7bc9e0a0f62b to your computer and use it in GitHub Desktop.
memory game with odin lang and raylib library
package main
import rl "vendor:raylib"
import "core:slice"
import "core:math/rand"
import "core:time"
import "core:fmt"
// Game constants
CARD_WIDTH :: 80
CARD_HEIGHT :: 120
GRID_ROWS :: 4
GRID_COLS :: 4
TOTAL_CARDS :: GRID_ROWS * GRID_COLS
SPACING :: 10
WINDOW_WIDTH :: GRID_COLS * (CARD_WIDTH + SPACING) + SPACING
WINDOW_HEIGHT :: GRID_ROWS * (CARD_HEIGHT + SPACING) + SPACING + 50 // Extra space for UI
// Card structure
Card :: struct {
symbol: rune, // Symbol on the card (e.g., 'A', 'B')
is_flipped: bool, // Is the card face-up?
is_matched: bool, // Has the card been matched?
position: rl.Vector2, // Position on screen
rect: rl.Rectangle, // Rectangle for collision detection
}
// Game state
Game :: struct {
cards: [TOTAL_CARDS]Card,
first_flipped_index: int, // Index of first card flipped (-1 if none)
second_flipped_index: int, // Index of second card flipped (-1 if none)
flip_timer: f32, // Timer for showing unmatched cards
matches_found: int, // Number of matched pairs
is_game_over: bool, // Is the game complete?
moves: int, // Number of moves made
}
main :: proc() {
// Initialize window
rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Card Match Game")
defer rl.CloseWindow()
rl.SetTargetFPS(60)
// Initialize game
game := init_game()
// Main game loop
for !rl.WindowShouldClose() {
update_game(&game)
draw_game(&game)
}
}
init_game :: proc() -> Game {
game: Game
game.first_flipped_index = -1
game.second_flipped_index = -1
game.flip_timer = 0
game.matches_found = 0
game.is_game_over = false
game.moves = 0
// Define symbols (half of TOTAL_CARDS for pairs)
symbols := []rune{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'}
temp_symbols: [TOTAL_CARDS]rune
// Create pairs
for i in 0..<len(symbols) {
temp_symbols[i] = symbols[i]
temp_symbols[i + len(symbols)] = symbols[i]
}
// Shuffle symbols using Fisher-Yates algorithm
// rand.global_rand_seed(u64(time.now()._nsec))
for i := len(temp_symbols)-1; i > 0; i -= 1 {
j := rand.int_max(i + 1)
temp_symbols[i], temp_symbols[j] = temp_symbols[j], temp_symbols[i]
}
// Initialize cards
for i in 0..<TOTAL_CARDS {
row := i / GRID_COLS
col := i % GRID_COLS
x := f32(col * (CARD_WIDTH + SPACING) + SPACING)
y := f32(row * (CARD_HEIGHT + SPACING) + SPACING)
game.cards[i] = Card{
symbol = temp_symbols[i],
is_flipped = false,
is_matched = false,
position = {x, y},
rect = {x, y, CARD_WIDTH, CARD_HEIGHT},
}
}
return game
}
update_game :: proc(game: ^Game) {
if game.is_game_over {
if rl.IsKeyPressed(.SPACE) {
// Restart game
game^ = init_game()
}
return
}
// Only allow flipping if we're not waiting for a match check
if game.first_flipped_index == -1 || (game.first_flipped_index != -1 && game.second_flipped_index == -1) {
if rl.IsMouseButtonPressed(.LEFT) {
mouse_pos := rl.GetMousePosition()
for i in 0..<len(game.cards) {
card := &game.cards[i]
if !card.is_flipped && !card.is_matched && rl.CheckCollisionPointRec(mouse_pos, card.rect) {
card.is_flipped = true
if game.first_flipped_index == -1 {
game.first_flipped_index = i
} else if game.second_flipped_index == -1 && i != game.first_flipped_index {
game.second_flipped_index = i
game.moves += 1
}
break
}
}
}
}
// Check for matches
if game.first_flipped_index != -1 && game.second_flipped_index != -1 {
game.flip_timer += rl.GetFrameTime()
if game.flip_timer >= 1.0 { // Show cards for 1 second
first_card := &game.cards[game.first_flipped_index]
second_card := &game.cards[game.second_flipped_index]
if first_card.symbol == second_card.symbol {
first_card.is_matched = true
second_card.is_matched = true
game.matches_found += 1
} else {
first_card.is_flipped = false
second_card.is_flipped = false
}
game.first_flipped_index = -1
game.second_flipped_index = -1
game.flip_timer = 0
}
}
// Check if game is over
if game.matches_found == TOTAL_CARDS / 2 {
game.is_game_over = true
}
}
draw_game :: proc(game: ^Game) {
rl.BeginDrawing()
defer rl.EndDrawing()
rl.ClearBackground(rl.Color{50, 50, 70, 255})
// Draw cards
for card in game.cards {
if card.is_matched {
rl.DrawRectangleRec(card.rect, rl.Color{100, 200, 100, 255})
} else if card.is_flipped {
rl.DrawRectangleRec(card.rect, rl.Color{240, 240, 240, 255})
// Draw symbol
symbol_cstr := fmt.ctprintf("%c", card.symbol)
text_width := rl.MeasureText(symbol_cstr, 40)
rl.DrawText(
symbol_cstr,
i32(card.rect.x + CARD_WIDTH/2 - f32(text_width)/2),
i32(card.rect.y + CARD_HEIGHT/2 - 20),
40,
rl.BLACK
)
} else {
rl.DrawRectangleRec(card.rect, rl.Color{80, 80, 120, 255})
}
// Draw border
rl.DrawRectangleLinesEx(card.rect, 3, rl.WHITE)
}
// Draw UI
moves_text := fmt.ctprintf("Moves: %d", game.moves)
matches_text := fmt.ctprintf("Matches: %d/%d", game.matches_found, TOTAL_CARDS/2)
rl.DrawText(moves_text, 10, WINDOW_HEIGHT - 40, 20, rl.WHITE)
rl.DrawText(matches_text, 10, WINDOW_HEIGHT - 20, 20, rl.WHITE)
// Draw game over screen
if game.is_game_over {
rl.DrawRectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, rl.Color{0, 0, 0, 180})
win_text := fmt.ctprintf("Congratulations! You won in %d moves!", game.moves)
restart_text : cstring = "Press SPACE to play again"
win_text_width := rl.MeasureText(win_text, 24)
restart_text_width := rl.MeasureText(restart_text, 20)
rl.DrawText(
win_text,
WINDOW_WIDTH/2 - win_text_width/2,
WINDOW_HEIGHT/2 - 30,
24,
rl.WHITE
)
rl.DrawText(
cstring(restart_text),
WINDOW_WIDTH/2 - restart_text_width/2,
WINDOW_HEIGHT/2 + 10,
20,
rl.WHITE
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment