Created
June 20, 2025 15:10
-
-
Save naranyala/b1ced095422ae17bf5fe7bc9e0a0f62b to your computer and use it in GitHub Desktop.
memory game with odin lang and raylib library
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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