hanafuda/game.c

1059 lines
32 KiB
C
Raw Permalink Normal View History

2025-02-26 20:05:31 -05:00
// Copyright 2025 Bill Rossi
//
// This file is part of Hanafuda Hachi-Hachi.
//
// Hanafuda Hachi-Hachi is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//
// Hanafuda Hachi-Hachi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Hanafuda Hachi-Hachi. If not, see <https://www.gnu.org/licenses/>.
2025-02-02 18:13:19 -05:00
#include <string.h>
#include <stdlib.h>
2025-02-02 18:30:45 -05:00
#include <stdio.h>
2025-02-02 18:13:19 -05:00
#include "game.h"
#include "card.h"
#include "teyaku.h"
#include "dekiyaku.h"
2025-02-04 18:47:56 -05:00
#include "field_multiplier.h"
2025-02-16 09:21:34 -05:00
#include "special_cases.h"
2025-02-08 07:55:28 -05:00
#include "play.h"
2025-02-20 07:09:57 -05:00
#include "dialog.h"
2025-02-02 18:13:19 -05:00
Vector2 mouse_pos;
char teyaku_calculation[400];
void initialize_game(Game *g) {
2025-02-23 09:54:46 -05:00
Image cards_image_red = LoadImage("img/cards_red.png");
2025-02-23 17:28:13 -05:00
g->cards_texture_red = LoadTextureFromImage(cards_image_red);
2025-02-23 09:54:46 -05:00
UnloadImage(cards_image_red);
Image cards_image_black = LoadImage("img/cards_black.png");
2025-02-23 17:28:13 -05:00
g->cards_texture_black = LoadTextureFromImage(cards_image_black);
2025-02-23 09:54:46 -05:00
UnloadImage(cards_image_black);
2025-02-20 19:54:55 -05:00
2025-02-02 19:07:49 -05:00
g->deck.count = 0;
2025-02-22 13:50:38 -05:00
g->deck.position = (Vector2) { 500, 300 };
2025-02-10 17:27:02 -05:00
g->deck.display_type = HAND_DISPLAY_DECK;
2025-02-02 18:13:19 -05:00
g->should_close = false;
2025-02-03 20:07:22 -05:00
g->state = GAME_STATE_INITIALIZING;
2025-02-04 18:47:56 -05:00
g->field_multiplier = NULL;
2025-02-20 07:09:57 -05:00
g->dialog = NULL;
g->kan_value = 12;
2025-02-26 20:16:04 -05:00
g->number_of_rounds = 3;
g->black_card_backs = true;
2025-02-26 20:16:04 -05:00
g->deal_speed = 0.4;
g->options = malloc(sizeof(Options));
2025-02-23 18:35:11 -05:00
initialize_title(g);
load_options_from_game(g);
2025-02-20 19:54:55 -05:00
2025-02-20 07:09:57 -05:00
init_dialogs(g);
2025-02-16 20:50:13 -05:00
2025-02-02 18:13:19 -05:00
for (int i = 0; i < 48; i++) {
CardType t = CHAFF;
RibbonType rt = RIBBON_NONE;
Month month = i / 4;
switch (i) {
case 0:
case 8:
case 28:
case 40:
case 44:
t = BRIGHT; break;
case 1:
case 5:
case 9:
t = RIBBON; rt = RIBBON_POETRY; break;
case 21:
case 33:
case 37:
t = RIBBON; rt = RIBBON_BLUE; break;
case 13:
case 17:
case 25:
case 42:
t = RIBBON; rt = RIBBON_PLAIN; break;
case 4:
case 12:
case 16:
case 20:
case 24:
case 29:
case 32:
case 36:
case 41:
t = ANIMAL; break;
}
2025-02-23 21:26:22 -05:00
g->cards[i] = (Card) { i, t, rt, month, { -500, -300 }, false };
2025-02-03 20:07:22 -05:00
g->cards[i].move.end_time = 0.;
g->cards[i].move.position = &g->cards[i].position;
2025-02-23 21:26:22 -05:00
g->cards[i].move.destination = (Vector2) { -500, -300 };
g->cards[i].order = i;
2025-02-19 06:04:10 -05:00
g->cards[i].selected = false;
2025-02-23 21:26:22 -05:00
g->cards[i].visible = false;
2025-02-25 05:29:50 -05:00
g->deck.cards[g->deck.count++] = &g->cards[i];
2025-02-02 18:13:19 -05:00
}
2025-02-22 11:47:34 -05:00
g->player.points = 100 * g->kan_value;
g->right.points = 100 * g->kan_value;
g->left.points = 100 * g->kan_value;
2025-02-26 19:47:18 -05:00
g->player.last_round_points = 100 * g->kan_value;
g->right.last_round_points = 100 * g->kan_value;
g->left.last_round_points = 100 * g->kan_value;
g->player.points_string[0] = '\0';
g->right.points_string[0] = '\0';
g->left.points_string[0] = '\0';
2025-02-23 05:10:42 -05:00
g->player.teyaku_string[0] = '\0';
g->right.teyaku_string[0] = '\0';
g->left.teyaku_string[0] = '\0';
2025-02-22 13:44:18 -05:00
g->player.name = "Player";
g->right.name = "Right";
g->left.name = "Left";
g->player.hand.count = 0;
g->right.hand.count = 0;
g->left.hand.count = 0;
g->field.count = 0;
g->player.scored.count = 0;
g->right.scored.count = 0;
g->left.scored.count = 0;
g->player.dekiyaku_score = 0;
g->left.dekiyaku_score = 0;
g->right.dekiyaku_score = 0;
2025-02-20 19:54:55 -05:00
g->player.teyaku.calculated = false;
g->right.teyaku.calculated = false;
g->left.teyaku.calculated = false;
2025-02-20 19:21:03 -05:00
2025-02-22 07:51:32 -05:00
g->player.seat = PLAYER;
g->right.seat = RIGHT;
g->left.seat = LEFT;
g->player.hand.position = (Vector2) { 300, 600 };
g->player.hand.display_type = HAND_DISPLAY_ROW;
g->right.hand.position = (Vector2) { 750, 125 };
g->right.hand.display_type = HAND_DISPLAY_ROW;
g->left.hand.position = (Vector2) { 50, 125 };
g->left.hand.display_type = HAND_DISPLAY_ROW;
g->player.scored.position = (Vector2) { 300, 750 };
g->player.scored.display_type = HAND_DISPLAY_SCORED;
g->right.scored.position = (Vector2) { 750, 25 };
g->right.scored.display_type = HAND_DISPLAY_SCORED;
g->left.scored.position = (Vector2) { 50, 25 };
g->left.scored.display_type = HAND_DISPLAY_SCORED;
2025-02-02 19:07:49 -05:00
2025-02-22 13:50:38 -05:00
g->field.position = (Vector2) { 600, 300 };
g->field.display_type = HAND_DISPLAY_FIELD;
2025-02-02 18:13:19 -05:00
strcpy(teyaku_calculation, "");
2025-02-22 07:36:55 -05:00
int dealer = rand() % 3;
switch (dealer) {
case PLAYER:
g->dealer = &g->player;
break;
case RIGHT:
g->dealer = &g->right;
break;
case LEFT:
g->dealer = &g->left;
break;
}
2025-02-22 12:01:00 -05:00
g->current_round = 0;
2025-02-23 18:07:57 -05:00
g->state = GAME_STATE_TITLE_SCREEN;
2025-02-02 18:13:19 -05:00
}
2025-02-19 06:04:10 -05:00
Player *current_player(Game *g) {
switch (g->turn_number % 3) {
case 0:
return &g->player;
case 1:
return &g->right;
case 2:
return &g->left;
}
2025-02-20 19:21:03 -05:00
return NULL;
2025-02-19 06:04:10 -05:00
}
bool is_player_turn(Game *g) {
return current_player(g) == &g->player;
}
2025-02-02 19:07:49 -05:00
void handle_input(Game *g) {
if (g->state == GAME_STATE_OPTIONS) {
return options_handle_input(g);
2025-02-21 06:15:08 -05:00
}
2025-02-22 11:47:34 -05:00
2025-02-23 18:07:57 -05:00
if (g->state == GAME_STATE_TITLE_SCREEN) {
return title_handle_input(g);
}
2025-02-22 10:48:48 -05:00
if (g->dialog) {
return dialog_handle_input(g->dialog);
}
2025-02-19 06:04:10 -05:00
2025-02-27 06:34:20 -05:00
/*
if (IsKeyPressed(KEY_R)) {
g->state = GAME_STATE_INITIALIZING;
return;
}
2025-02-27 06:34:20 -05:00
if (IsKeyPressed(KEY_S)) {
ExportImage(LoadImageFromScreen(), "img.png");
}
*/
2025-02-22 11:47:34 -05:00
if (!is_player_turn(g)) return;
2025-02-06 19:44:48 -05:00
switch (g->state) {
2025-02-19 06:04:10 -05:00
case GAME_STATE_CHOOSING_FROM_HAND:
2025-02-06 19:44:48 -05:00
if (IsMouseButtonPressed(0)) {
mouse_pos = GetMousePosition();
for (int i = 0; i < g->player.hand.count; i++) {
if (point_within_card(g->player.hand.cards[i], mouse_pos)) {
2025-02-07 19:07:17 -05:00
for (int j = 0; j < 48; j++) {
g->cards[j].selected = false;
}
g->player.hand.cards[i]->selected = true;
2025-02-06 19:44:48 -05:00
break;
}
2025-02-02 18:13:19 -05:00
}
}
2025-02-06 19:44:48 -05:00
break;
2025-02-19 06:04:10 -05:00
case GAME_STATE_CHOOSING_TARGET:
2025-02-07 19:07:17 -05:00
if (IsMouseButtonPressed(0)) {
mouse_pos = GetMousePosition();
for (int i = 0; i < g->player.hand.count; i++) {
if (point_within_card(g->player.hand.cards[i], mouse_pos)) {
if (g->player.hand.cards[i]->selected) {
g->player.hand.cards[i]->selected = false;
2025-02-07 19:07:17 -05:00
} else {
for (int j = 0; j < g->player.hand.count; j++) {
g->player.hand.cards[j]->selected = false;
2025-02-07 19:07:17 -05:00
}
g->player.hand.cards[i]->selected = true;
2025-02-07 19:07:17 -05:00
}
}
}
2025-02-08 07:55:28 -05:00
Card *selected_card = NULL;
for (int i = 0; i < g->player.hand.count; i++) {
if (g->player.hand.cards[i]->selected) {
selected_card = g->player.hand.cards[i];
2025-02-08 07:55:28 -05:00
break;
}
}
for (int i = 0; i < g->field.count; i++) {
if (point_within_card(g->field.cards[i], mouse_pos)) {
if (selected_card == NULL) printf("No card selected, whoops");
if (valid_play(&g->field, selected_card, g->field.cards[i])) {
2025-02-08 08:15:43 -05:00
g->current_play_from_hand = selected_card;
g->current_play_target = g->field.cards[i];
2025-02-08 07:55:28 -05:00
} else {
printf("Invalid\n");
}
2025-02-10 17:27:02 -05:00
break;
2025-02-08 07:55:28 -05:00
}
}
if (CheckCollisionPointRec(mouse_pos, next_card_position(&g->field))) {
if (valid_play(&g->field, selected_card, NULL)) {
2025-02-08 08:15:43 -05:00
g->current_play_from_hand = selected_card;
g->current_play_target = NULL;
2025-02-08 07:55:28 -05:00
} else {
printf("Invalid\n");
}
}
2025-02-07 19:07:17 -05:00
}
2025-02-06 19:44:48 -05:00
break;
case GAME_STATE_CHOOSING_TARGET_FROM_DECK:
if (IsMouseButtonPressed(0)) {
mouse_pos = GetMousePosition();
for (int i = 0; i < g->field.count; i++) {
if (point_within_card(g->field.cards[i], mouse_pos)) {
Card *selected_card = g->deck.cards[g->deck.count - 1];
if (valid_play(&g->field, selected_card, g->field.cards[i])) {
g->current_play_from_hand = selected_card;
g->current_play_target = g->field.cards[i];
} else {
printf("Invalid\n");
}
break;
}
}
}
break;
2025-02-06 19:44:48 -05:00
default:
break;
2025-02-02 18:13:19 -05:00
}
}
2025-02-21 06:15:08 -05:00
void capture_card_from_field(Game *g, Card *played, Card *target, Hand *hand, Hand *scored) {
// capture all three cards if they play the fourth
Card *same_month_card[3];
int same_month_card_count = 0;
for (int i = 0; i < g->field.count; i++) {
Card *c = g->field.cards[i];
if (c->month == played->month) {
same_month_card[same_month_card_count++] = c;
}
}
remove_from_hand(hand, played);
2025-02-23 06:09:57 -05:00
add_to_hand(scored, played, g->deal_speed);
2025-02-21 06:15:08 -05:00
if (same_month_card_count == 3) {
for (int i = 0; i < 3; i++) {
remove_from_hand(&g->field, same_month_card[i]);
2025-02-23 06:09:57 -05:00
add_to_hand(scored, same_month_card[i], g->deal_speed);
2025-02-21 06:15:08 -05:00
}
} else {
remove_from_hand(&g->field, target);
2025-02-23 06:09:57 -05:00
add_to_hand(scored, target, g->deal_speed);
2025-02-21 06:15:08 -05:00
}
}
2025-02-19 06:04:10 -05:00
void run_frame_ai_playing(Game *g) {
Hand *hand = &current_player(g)->hand;
Hand *scored = &current_player(g)->scored;
2025-02-15 15:10:26 -05:00
Play play = ai_play(hand, &g->field);
play.played->visible = true;
if (play.target) {
2025-02-21 06:15:08 -05:00
capture_card_from_field(g, play.played, play.target, hand, scored);
2025-02-15 15:10:26 -05:00
} else {
remove_from_hand(hand, play.played);
2025-02-23 06:09:57 -05:00
add_to_hand(&g->field, play.played, g->deal_speed);
2025-02-15 15:10:26 -05:00
}
}
2025-02-20 19:21:03 -05:00
void run_frame_initializing(Game *g) {
2025-02-22 07:36:55 -05:00
g->turn_number = g->dealer->seat;
2025-02-20 19:54:55 -05:00
g->player.hand.count = 0;
g->right.hand.count = 0;
g->left.hand.count = 0;
g->field.count = 0;
g->player.scored.count = 0;
g->right.scored.count = 0;
g->left.scored.count = 0;
g->player.dekiyaku_score = 0;
g->left.dekiyaku_score = 0;
g->right.dekiyaku_score = 0;
g->player.dekiyaku_action = DEKIYAKU_ACTION_NONE;
g->right.dekiyaku_action = DEKIYAKU_ACTION_NONE;
g->left.dekiyaku_action = DEKIYAKU_ACTION_NONE;
strcpy(g->player.teyaku_string, "");
strcpy(g->right.teyaku_string, "");
strcpy(g->left.teyaku_string, "");
2025-02-20 19:54:55 -05:00
g->current_play_from_hand = NULL;
g->current_play_target = NULL;
2025-02-25 20:35:50 -05:00
g->first_sage = NULL;
2025-02-21 06:15:08 -05:00
g->deck.count = 0;
2025-02-20 19:54:55 -05:00
for (int i = 0; i < 48; i++) {
Card *c = &g->cards[i];
c->visible = false;
2025-02-25 05:55:03 -05:00
c->selected = false;
2025-02-23 06:09:57 -05:00
add_to_hand(&g->deck, c, g->deal_speed);
2025-02-20 19:54:55 -05:00
}
2025-02-26 19:47:18 -05:00
shuffle_hand(&g->deck);
// order_deck(&g->deck);
2025-02-20 19:54:55 -05:00
kan_points_string(g, g->player.points, g->player.points_string);
kan_points_string(g, g->right.points, g->right.points_string);
kan_points_string(g, g->left.points, g->left.points_string);
2025-02-20 19:21:03 -05:00
g->state = GAME_STATE_DEALING;
}
2025-02-21 06:38:24 -05:00
bool misdeal(Game *g) {
int month_count[12] = { 0,0,0,0,0,0,0,0,0,0,0,0 };
for (int i = 0; i < g->field.count; i++) {
Card *c = g->field.cards[i];
month_count[c->month]++;
}
for (int i = 0; i < 12; i++)
if (month_count[i] >= 4) return true;
return false;
}
2025-02-20 19:21:03 -05:00
void run_frame_dealing(Game *g) {
2025-02-22 07:36:55 -05:00
if (current_player(g)->hand.count < 4) {
2025-02-23 06:09:57 -05:00
deal(&g->deck, &current_player(g)->hand, 4, is_player_turn(g), g->deal_speed);
2025-02-22 07:36:55 -05:00
g->turn_number++;
2025-02-04 18:47:56 -05:00
} else if (g->field.count < 3) {
2025-02-23 06:09:57 -05:00
deal(&g->deck, &g->field, 3, true, g->deal_speed);
2025-02-22 07:36:55 -05:00
} else if (current_player(g)->hand.count < 7) {
2025-02-23 06:09:57 -05:00
deal(&g->deck, &current_player(g)->hand, 3, is_player_turn(g), g->deal_speed);
2025-02-22 07:36:55 -05:00
g->turn_number++;
2025-02-04 18:47:56 -05:00
} else if (g->field.count < 6) {
2025-02-23 06:09:57 -05:00
deal(&g->deck, &g->field, 3, true, g->deal_speed);
2025-02-03 20:07:22 -05:00
} else {
2025-02-21 06:38:24 -05:00
if (misdeal(g)) {
printf("misdeal\n");
g->state = GAME_STATE_INITIALIZING;
} else {
2025-02-22 07:36:55 -05:00
g->turn_number++;
2025-02-21 06:38:24 -05:00
g->state = GAME_STATE_CALCULATING_FIELD_MULTIPLIER;
}
2025-02-03 20:07:22 -05:00
}
}
2025-02-04 18:47:56 -05:00
void run_frame_calculating_field_multiplier(Game *g) {
g->field_multiplier = calculate_field_multiplier(&g->field);
g->state = GAME_STATE_CALCULATING_TEYAKU;
}
void run_frame_calculating_teyaku(Game *g) {
calculate_teyaku(g->player.hand, &g->player.teyaku);
2025-02-23 05:10:42 -05:00
teyaku_to_string(&g->player.teyaku, g->player.teyaku_string);
2025-02-19 06:04:10 -05:00
calculate_teyaku(g->right.hand, &g->right.teyaku);
2025-02-23 05:10:42 -05:00
teyaku_to_string(&g->right.teyaku, g->right.teyaku_string);
2025-02-19 06:04:10 -05:00
calculate_teyaku(g->left.hand, &g->left.teyaku);
2025-02-23 05:10:42 -05:00
teyaku_to_string(&g->left.teyaku, g->left.teyaku_string);
if (teyaku_points(&g->player.teyaku) > 0) {
teyaku_dialog(g);
set_teyaku_to_string(&g->player.teyaku, g->dialog->text[1]);
chaff_teyaku_to_string(&g->player.teyaku, g->dialog->text[2]);
} else {
g->state = GAME_STATE_START_OF_TURN;
}
2025-02-19 06:04:10 -05:00
}
void run_frame_start_of_turn(Game *g) {
g->turn_number++;
if(g->player.hand.count == 0 && g->right.hand.count == 0 && g->left.hand.count == 0) {
2025-02-25 20:35:50 -05:00
if (
g->player.dekiyaku_action == DEKIYAKU_ACTION_NONE &&
g->left.dekiyaku_action == DEKIYAKU_ACTION_NONE &&
g->right.dekiyaku_action == DEKIYAKU_ACTION_NONE) {
g->state = GAME_STATE_CALCULATING_SCORES;
} else {
g->state = GAME_STATE_CALCULATING_DEKIYAKU_SCORE;
}
2025-02-19 06:04:10 -05:00
} else {
g->state = GAME_STATE_CHECKING_FOR_CANCEL;
}
}
void run_frame_checking_for_cancel(Game *g) {
if (current_player(g)->dekiyaku_action == DEKIYAKU_ACTION_SAGE) {
if (is_player_turn(g)) {
2025-02-22 10:48:48 -05:00
cancel_dialog(g);
2025-02-19 06:04:10 -05:00
} else {
2025-02-20 19:21:03 -05:00
// TODO: the AI might want to cancel at some point
2025-02-19 06:04:10 -05:00
g->state = GAME_STATE_CHOOSING_FROM_HAND;
}
} else {
g->state = GAME_STATE_CHOOSING_FROM_HAND;
2025-02-07 19:07:17 -05:00
}
2025-02-06 19:44:48 -05:00
}
void run_frame_player_choosing_from_hand(Game *g) {
for (int i = 0; i < g->player.hand.count; i++) {
if (g->player.hand.cards[i]->selected) {
2025-02-19 06:04:10 -05:00
g->state = GAME_STATE_CHOOSING_TARGET;
2025-02-08 08:15:43 -05:00
return;
2025-02-07 19:07:17 -05:00
}
}
2025-02-06 19:44:48 -05:00
}
2025-02-07 19:07:17 -05:00
2025-02-19 06:04:10 -05:00
void run_frame_choosing_from_hand(Game *g) {
if (is_player_turn(g)) {
run_frame_player_choosing_from_hand(g);
} else {
run_frame_ai_playing(g);
g->state = GAME_STATE_SHOWING_CARD_FROM_DECK;
2025-02-19 06:04:10 -05:00
}
}
void run_frame_choosing_target(Game *g) {
if (!is_player_turn(g)) {
printf("An AI player is choosing a target. That ain't right\n");
return;
}
2025-02-25 05:55:03 -05:00
Card *selected_card = NULL;
2025-02-07 19:07:17 -05:00
bool no_cards_selected = true;
for (int i = 0; i < g->player.hand.count; i++) {
if (g->player.hand.cards[i]->selected) {
2025-02-25 05:55:03 -05:00
selected_card = g->player.hand.cards[i];
2025-02-07 19:07:17 -05:00
break;
}
}
2025-02-25 05:55:03 -05:00
if (!selected_card) {
2025-02-19 06:04:10 -05:00
g->state = GAME_STATE_CHOOSING_FROM_HAND;
2025-02-08 08:15:43 -05:00
return;
}
2025-02-25 05:55:03 -05:00
for (int i = 0; i < g->field.count; i++) {
g->field.cards[i]->selected = g->field.cards[i]->month == selected_card->month;
}
2025-02-08 08:15:43 -05:00
if (g->current_play_from_hand) {
if (g->current_play_target) {
g->current_play_from_hand->selected = false;
2025-02-21 06:15:08 -05:00
capture_card_from_field(
g,
g->current_play_from_hand,
g->current_play_target,
&g->player.hand,
&g->player.scored
);
2025-02-08 08:15:43 -05:00
g->current_play_from_hand = NULL;
g->current_play_target = NULL;
} else {
g->current_play_from_hand->selected = false;
remove_from_hand(&g->player.hand, g->current_play_from_hand);
2025-02-23 06:09:57 -05:00
add_to_hand(&g->field, g->current_play_from_hand, g->deal_speed);
2025-02-08 08:15:43 -05:00
g->current_play_from_hand = NULL;
}
2025-02-25 05:55:03 -05:00
for (int i = 0; i < g->player.scored.count; i++) {
g->player.scored.cards[i]->selected = false;
}
g->state = GAME_STATE_SHOWING_CARD_FROM_DECK;
2025-02-10 17:27:02 -05:00
}
}
void run_frame_showing_card_from_deck(Game *g) {
Card *top_card = g->deck.cards[g->deck.count - 1];
remove_from_hand(&g->deck, top_card);
2025-02-23 06:09:57 -05:00
add_to_hand(&g->deck, top_card, g->deal_speed);
top_card->visible = true;
top_card->move.end_time = 0.3;
2025-02-22 13:50:38 -05:00
top_card->move.destination.y = top_card->move.destination.y + 150;
g->state = GAME_STATE_PLAYING_FROM_DECK;
}
2025-02-19 06:04:10 -05:00
void run_frame_playing_from_deck(Game *g) {
Hand *to_hand = &current_player(g)->scored;
Card *top_card = g->deck.cards[g->deck.count - 1];
Card *targets[4];
int target_count = 0;
valid_targets(top_card, &g->field, &targets[0], &target_count);
if (target_count == 1) {
capture_card_from_field(g, top_card, targets[0], &g->deck, to_hand);
2025-02-19 06:04:10 -05:00
g->state = GAME_STATE_CHECKING_FOR_NEW_DEKIYAKU;
} else if(target_count == 0) {
2025-02-19 06:04:10 -05:00
remove_from_hand(&g->deck, top_card);
2025-02-23 06:09:57 -05:00
add_to_hand(&g->field, top_card, g->deal_speed);
g->state = GAME_STATE_CHECKING_FOR_NEW_DEKIYAKU;
} else {
g->state = GAME_STATE_CHOOSING_TARGET_FROM_DECK;
return;
}
}
void run_frame_choosing_target_from_deck(Game *g) {
Hand *to_hand = &current_player(g)->scored;
Card *top_card = g->deck.cards[g->deck.count - 1];
Card *targets[4];
int target_count = 0;
valid_targets(top_card, &g->field, &targets[0], &target_count);
if (is_player_turn(g)) {
2025-02-25 05:55:03 -05:00
for (int i = 0; i < target_count; i++) {
targets[i]->selected = true;
}
if (g->current_play_target) {
2025-02-25 05:55:03 -05:00
for (int i = 0; i < target_count; i++) {
targets[i]->selected = false;
}
capture_card_from_field(g, top_card, g->current_play_target, &g->deck, to_hand);
g->state = GAME_STATE_CHECKING_FOR_NEW_DEKIYAKU;
g->current_play_from_hand = NULL;
g->current_play_target = NULL;
}
} else {
// TODO: better AI
capture_card_from_field(g, top_card, targets[0], &g->deck, to_hand);
g->state = GAME_STATE_CHECKING_FOR_NEW_DEKIYAKU;
2025-02-16 20:50:13 -05:00
}
}
2025-02-19 06:04:10 -05:00
void run_frame_checking_for_new_dekiyaku(Game *g) {
Player *cp = current_player(g);
calculate_dekiyaku(&cp->scored, &cp->dekiyaku);
int new_score = dekiyaku_score(&cp->dekiyaku);
if (new_score != cp->dekiyaku_score) {
cp->dekiyaku_score = new_score;
2025-02-20 19:21:03 -05:00
if (is_player_turn(g)) {
2025-02-22 10:48:48 -05:00
shoubu_dialog(g);
2025-02-20 19:21:03 -05:00
} else {
// TODO: better AI
2025-02-25 20:35:50 -05:00
// TODO: if it can sage, set "first sage" if possible
2025-02-20 19:21:03 -05:00
cp->dekiyaku_action = DEKIYAKU_ACTION_SHOUBU;
g->state = GAME_STATE_CALCULATING_DEKIYAKU_SCORE;
}
2025-02-19 06:04:10 -05:00
} else {
g->state = GAME_STATE_START_OF_TURN;
2025-02-16 20:50:13 -05:00
}
2025-02-15 15:10:26 -05:00
}
2025-02-19 06:04:10 -05:00
void run_frame_selecting_dekiyaku_action(Game *g) {
if (is_player_turn(g)) {
if (g->player.dekiyaku_action != DEKIYAKU_ACTION_NONE) {
g->state = GAME_STATE_CALCULATING_DEKIYAKU_SCORE;
2025-02-16 09:11:50 -05:00
} else {
2025-02-22 10:48:48 -05:00
shoubu_dialog(g);
2025-02-16 09:11:50 -05:00
}
2025-02-19 06:04:10 -05:00
} else {
// TODO: better AI
current_player(g)->dekiyaku_action = DEKIYAKU_ACTION_SHOUBU;
g->state = GAME_STATE_CALCULATING_DEKIYAKU_SCORE;
2025-02-16 09:11:50 -05:00
}
}
void pay_teyaku_to_player(Game *g, Player *p) {
int points = teyaku_points(&p->teyaku);
transfer_kan(g, &p->points, &g->player.points, points);
transfer_kan(g, &p->points, &g->right.points, points);
transfer_kan(g, &p->points, &g->left.points, points);
}
void pay_teyaku(Game *g) {
pay_teyaku_to_player(g, &g->player);
pay_teyaku_to_player(g, &g->right);
pay_teyaku_to_player(g, &g->left);
}
2025-02-16 09:11:50 -05:00
void run_frame_calculating_scores(Game *g) {
2025-02-22 11:47:34 -05:00
no_dekiyaku_end_of_round_dialog(g);
2025-02-22 07:36:55 -05:00
int hp[3];
hp[0] = hand_points(&g->player.scored);
hp[1] = hand_points(&g->right.scored);
hp[2] = hand_points(&g->left.scored);
2025-02-16 09:21:34 -05:00
SpecialCase special_case = calculate_special_case(g);
switch(special_case.type) {
case SPECIAL_CASE_ALL_EIGHTS:
2025-02-22 11:47:34 -05:00
sprintf(g->dialog->text[0], "All eights!");
sprintf(g->dialog->text[0], "Dealer gets %d kan", special_case.score);
2025-02-22 07:36:55 -05:00
transfer_kan(g, &g->dealer->points, &g->player.points, special_case.score);
transfer_kan(g, &g->dealer->points, &g->right.points, special_case.score);
transfer_kan(g, &g->dealer->points, &g->left.points, special_case.score);
2025-02-16 09:21:34 -05:00
break;
case SPECIAL_CASE_DOUBLE_EIGHTS:
case SPECIAL_CASE_SIXTEEN_CHAFF:
2025-02-22 12:01:00 -05:00
if (special_case.type == SPECIAL_CASE_DOUBLE_EIGHTS) sprintf(g->dialog->text[0], "Double eights!");
else sprintf(g->dialog->text[0], "Sixteen chaff!");
2025-02-22 11:47:34 -05:00
sprintf(g->dialog->text[1], "Player %d gets %d kan\n", special_case.target, special_case.score);
switch (special_case.target) {
case SPECIAL_CASE_TARGET_PLAYER:
transfer_kan(g, &g->player.points, &g->right.points, special_case.score);
transfer_kan(g, &g->player.points, &g->left.points, special_case.score);
2025-02-22 07:36:55 -05:00
g->dealer = &g->player;
break;
case SPECIAL_CASE_TARGET_RIGHT:
transfer_kan(g, &g->right.points, &g->player.points, special_case.score);
transfer_kan(g, &g->right.points, &g->left.points, special_case.score);
2025-02-22 07:36:55 -05:00
g->dealer = &g->right;
break;
case SPECIAL_CASE_TARGET_LEFT:
transfer_kan(g, &g->left.points, &g->right.points, special_case.score);
transfer_kan(g, &g->left.points, &g->player.points, special_case.score);
2025-02-22 07:36:55 -05:00
g->dealer = &g->left;
break;
}
break;
default:
2025-02-22 11:47:34 -05:00
sprintf(g->dialog->text[0], "Player score: %d", hp[0]);
sprintf(g->dialog->text[1], "Right score: %d", hp[1]);
sprintf(g->dialog->text[2], "Left score: %d", hp[2]);
2025-02-22 07:36:55 -05:00
transfer_points(g, &g->player.points, &g->temp_points, hp[0]);
transfer_points(g, &g->right.points, &g->temp_points, hp[1]);
transfer_points(g, &g->left.points, &g->temp_points, hp[2]);
if (hp[0] > hp[1]) {
if (hp[0] > hp[2]) g->dealer = &g->player;
else
if (hp[2] > hp[1]) g->dealer = &g->left;
else g->dealer = &g->right;
} else {
if (hp[1] > hp[2]) g->dealer = &g->right;
else g->dealer = &g->left;
}
pay_teyaku(g);
2025-02-16 09:21:34 -05:00
break;
}
2025-02-04 18:47:56 -05:00
}
2025-02-25 20:35:50 -05:00
int calculate_dekiyaku_score(Game *g, Player *p) {
2025-02-22 13:44:18 -05:00
Dekiyaku d;
calculate_dekiyaku(&p->scored, &d);
if (p->dekiyaku_action == DEKIYAKU_ACTION_SHOUBU) {
sprintf(g->dialog->text[0], "%s wins with dekiyaku!", p->name);
dekiyaku_to_string(&d, g->dialog->text[1]);
sprintf(g->dialog->text[2], "%s wins %d kan", p->name, dekiyaku_score(&d));
transfer_kan(g, &p->points, &g->player.points, dekiyaku_score(&d));
transfer_kan(g, &p->points, &g->right.points, dekiyaku_score(&d));
transfer_kan(g, &p->points, &g->left.points, dekiyaku_score(&d));
2025-02-25 20:35:50 -05:00
return dekiyaku_score(&d) * g->kan_value;
2025-02-22 13:44:18 -05:00
} else {
sprintf(g->dialog->text[0], "%s cancels with dekiyaku!", p->name);
dekiyaku_to_string(&d, g->dialog->text[1]);
if (dekiyaku_score(&d) % 2) {
sprintf(g->dialog->text[2], "%s wins %d.5 kan", p->name, dekiyaku_score(&d) / 2);
transfer_points(g, &p->points, &g->player.points, (dekiyaku_score(&d) * g->kan_value) / 2);
transfer_points(g, &p->points, &g->right.points, (dekiyaku_score(&d) * g->kan_value) / 2);
transfer_points(g, &p->points, &g->left.points, (dekiyaku_score(&d) * g->kan_value) / 2);
} else {
sprintf(g->dialog->text[2], "%s wins %d kan", p->name, dekiyaku_score(&d) / 2);
transfer_kan(g, &p->points, &g->player.points, dekiyaku_score(&d) / 2);
transfer_kan(g, &p->points, &g->right.points, dekiyaku_score(&d) / 2);
transfer_kan(g, &p->points, &g->left.points, dekiyaku_score(&d) / 2);
}
2025-02-25 20:35:50 -05:00
return (dekiyaku_score(&d) * g->kan_value) / 2;
2025-02-22 13:44:18 -05:00
}
}
2025-02-25 20:35:50 -05:00
void calculate_exhausted_dekiyaku_score(Game *g) {
g->player.dekiyaku_action = DEKIYAKU_ACTION_CANCEL;
int p_score = calculate_dekiyaku_score(g, &g->player);
g->right.dekiyaku_action = DEKIYAKU_ACTION_CANCEL;
int r_score = calculate_dekiyaku_score(g, &g->right);
g->left.dekiyaku_action = DEKIYAKU_ACTION_CANCEL;
int l_score = calculate_dekiyaku_score(g, &g->left);
printf("%d %d %d\n", p_score, r_score, l_score);
sprintf(g->dialog->text[0], "Hands are exhausted (half dekiyaku scores)");
sprintf(g->dialog->text[1], "Player scores %d points", p_score);
sprintf(g->dialog->text[2], "Right scores %d points", r_score);
sprintf(g->dialog->text[3], "Left scores %d points", l_score);
}
2025-02-19 06:04:10 -05:00
void run_frame_calculating_dekiyaku_score(Game *g) {
2025-02-22 13:44:18 -05:00
dekiyaku_end_of_round_dialog(g);
2025-02-26 20:16:04 -05:00
strcpy(g->dialog->text[3], "");
2025-02-22 13:44:18 -05:00
if (g->player.dekiyaku_action == DEKIYAKU_ACTION_CANCEL || g->player.dekiyaku_action == DEKIYAKU_ACTION_SHOUBU) {
calculate_dekiyaku_score(g, &g->player);
} else if (g->right.dekiyaku_action == DEKIYAKU_ACTION_CANCEL || g->right.dekiyaku_action == DEKIYAKU_ACTION_SHOUBU) {
calculate_dekiyaku_score(g, &g->right);
} else if (g->left.dekiyaku_action == DEKIYAKU_ACTION_CANCEL || g->left.dekiyaku_action == DEKIYAKU_ACTION_SHOUBU) {
calculate_dekiyaku_score(g, &g->left);
} else {
2025-02-25 20:35:50 -05:00
g->dealer = g->first_sage;
calculate_exhausted_dekiyaku_score(g);
2025-02-22 13:44:18 -05:00
}
pay_teyaku(g);
2025-02-19 06:04:10 -05:00
}
2025-02-22 12:01:00 -05:00
void run_frame_end_of_round(Game *g) {
2025-02-26 19:47:18 -05:00
end_of_round_dialog(g);
int player_diff = g->player.points - g->player.last_round_points;
if (player_diff > 0) {
sprintf(g->dialog->text[0], "Player gains %d points, has %d", player_diff, g->player.points);
} else if (player_diff < 0) {
sprintf(g->dialog->text[0], "Player loses %d points, has %d", -player_diff, g->player.points);
} else {
sprintf(g->dialog->text[0], "Player gains no points, has %d", g->player.points);
}
int right_diff = g->right.points - g->right.last_round_points;
if (right_diff > 0) {
sprintf(g->dialog->text[1], "Right gains %d points, has %d", right_diff, g->right.points);
} else if (right_diff < 0) {
sprintf(g->dialog->text[1], "Right loses %d points, has %d", -right_diff, g->right.points);
} else {
sprintf(g->dialog->text[1], "Right gains no points, has %d", g->right.points);
}
int left_diff = g->left.points - g->left.last_round_points;
if (left_diff > 0) {
sprintf(g->dialog->text[2], "Left gains %d points, has %d", left_diff, g->left.points);
} else if (left_diff < 0) {
sprintf(g->dialog->text[2], "Left loses %d points, has %d", -left_diff, g->left.points);
} else {
sprintf(g->dialog->text[2], "Left gains no points, has %d", g->left.points);
}
2025-02-22 14:02:32 -05:00
kan_points_string(g, g->player.points, g->player.points_string);
kan_points_string(g, g->right.points, g->right.points_string);
kan_points_string(g, g->left.points, g->left.points_string);
2025-02-22 12:01:00 -05:00
g->current_round++;
if (g->current_round >= g->number_of_rounds) {
g->state = GAME_STATE_END_OF_GAME;
} else {
g->state = GAME_STATE_INITIALIZING;
}
}
2025-02-22 14:32:09 -05:00
void *winning_player_string(Game *g, char *string) {
int p = g->player.points;
int r = g->right.points;
int l = g->left.points;
if (p == r && p == l) sprintf(string, "It's a three-way tie!");
else if (p > r && p == l) sprintf(string, "%s and %s tie for the win!", g->player.name, g->left.name);
else if (p > l && p == r) sprintf(string, "%s and %s tie for the win!", g->player.name, g->right.name);
else if (p < l && l == r) sprintf(string, "%s and %s tie for the win!", g->right.name, g->left.name);
else if (p > l && p > r) sprintf(string, "%s wins!", g->player.name);
else if (l > r && l > p) sprintf(string, "%s wins!", g->left.name);
else if (r > l && r > p) sprintf(string, "%s wins!", g->right.name);
else sprintf(string, "I have no idea who wins (%d %d %d) wins!", p, r, l);
}
2025-02-22 12:01:00 -05:00
void run_frame_end_of_game(Game *g) {
end_of_game_dialog(g);
2025-02-22 14:32:09 -05:00
char line[200];
kan_points_string(g, g->player.points, &line[0]);
sprintf(g->dialog->text[0], "Player: %s", line);
kan_points_string(g, g->right.points, &line[0]);
sprintf(g->dialog->text[1], "Right: %s", line);
kan_points_string(g, g->left.points, &line[0]);
sprintf(g->dialog->text[2], "Left: %s", line);
winning_player_string(g, g->dialog->text[3]);
}
void run_frame_new_game(Game *g) {
g->field_multiplier = NULL;
g->dialog = NULL;
g->player.points = 100 * g->kan_value;
g->right.points = 100 * g->kan_value;
g->left.points = 100 * g->kan_value;
g->player.points_string[0] = '\0';
g->right.points_string[0] = '\0';
g->left.points_string[0] = '\0';
strcpy(teyaku_calculation, "");
int dealer = rand() % 3;
switch (dealer) {
case PLAYER:
g->dealer = &g->player;
break;
case RIGHT:
g->dealer = &g->right;
break;
case LEFT:
g->dealer = &g->left;
break;
}
g->current_round = 0;
g->state = GAME_STATE_INITIALIZING;
2025-02-22 12:01:00 -05:00
}
2025-02-20 19:21:03 -05:00
void move_cards(Game *g) {
2025-02-03 20:07:22 -05:00
float delta = GetFrameTime();
for (int i = 0; i < 48; i++) {
move_position(&g->cards[i].move, delta);
}
2025-02-20 19:21:03 -05:00
}
bool done_moving(Game *g) {
for (int i = 0; i < 48; i++) {
if (!card_done_moving(&g->cards[i])) return false;
}
return true;
}
void run_frame(Game *g) {
handle_input(g);
if (g->dialog) return;
move_cards(g);
2025-02-23 21:26:22 -05:00
if (g->state == GAME_STATE_TITLE_SCREEN) run_frame_title(g);
2025-02-20 19:21:03 -05:00
if (!done_moving(g)) return;
2025-02-03 20:07:22 -05:00
switch (g->state) {
case GAME_STATE_INITIALIZING:
2025-02-20 19:21:03 -05:00
run_frame_initializing(g);
2025-02-03 20:07:22 -05:00
return;
case GAME_STATE_DEALING:
run_frame_dealing(g);
break;
2025-02-04 18:47:56 -05:00
case GAME_STATE_CALCULATING_FIELD_MULTIPLIER:
run_frame_calculating_field_multiplier(g);
break;
case GAME_STATE_CALCULATING_TEYAKU:
run_frame_calculating_teyaku(g);
break;
2025-02-19 06:04:10 -05:00
case GAME_STATE_START_OF_TURN:
run_frame_start_of_turn(g);
2025-02-06 19:44:48 -05:00
break;
2025-02-19 06:04:10 -05:00
case GAME_STATE_CHECKING_FOR_CANCEL:
run_frame_checking_for_cancel(g);
2025-02-06 19:44:48 -05:00
break;
2025-02-19 06:04:10 -05:00
case GAME_STATE_CHOOSING_FROM_HAND:
run_frame_choosing_from_hand(g);
2025-02-10 17:27:02 -05:00
break;
2025-02-19 06:04:10 -05:00
case GAME_STATE_CHOOSING_TARGET:
run_frame_choosing_target(g);
2025-02-15 15:10:26 -05:00
break;
case GAME_STATE_SHOWING_CARD_FROM_DECK:
run_frame_showing_card_from_deck(g);
break;
2025-02-19 06:04:10 -05:00
case GAME_STATE_PLAYING_FROM_DECK:
run_frame_playing_from_deck(g);
2025-02-15 15:10:26 -05:00
break;
case GAME_STATE_CHOOSING_TARGET_FROM_DECK:
run_frame_choosing_target_from_deck(g);
break;
2025-02-19 06:04:10 -05:00
case GAME_STATE_CHECKING_FOR_NEW_DEKIYAKU:
run_frame_checking_for_new_dekiyaku(g);
2025-02-15 15:10:26 -05:00
break;
2025-02-19 06:04:10 -05:00
case GAME_STATE_SELECTING_DEKIYAKU_ACTION:
run_frame_selecting_dekiyaku_action(g);
2025-02-15 15:10:26 -05:00
break;
2025-02-16 09:11:50 -05:00
case GAME_STATE_CALCULATING_SCORES:
run_frame_calculating_scores(g);
break;
2025-02-19 06:04:10 -05:00
case GAME_STATE_CALCULATING_DEKIYAKU_SCORE:
run_frame_calculating_dekiyaku_score(g);
break;
2025-02-22 12:01:00 -05:00
case GAME_STATE_END_OF_ROUND:
run_frame_end_of_round(g);
break;
case GAME_STATE_END_OF_GAME:
run_frame_end_of_game(g);
break;
2025-02-22 14:32:09 -05:00
case GAME_STATE_NEW_GAME:
run_frame_new_game(g);
break;
2025-02-23 18:07:57 -05:00
case GAME_STATE_TITLE_SCREEN:
break;
2025-02-23 21:26:22 -05:00
case GAME_STATE_OPTIONS:
break;
2025-02-03 20:07:22 -05:00
}
}
2025-02-23 21:26:22 -05:00
Texture *cards_texture_fun(Game *g) {
2025-02-23 17:28:13 -05:00
return g->black_card_backs ? &g->cards_texture_black : &g->cards_texture_red;
}
void draw_player_cards(Game *g, Player *p) {
for (int i = 0; i < p->hand.count; i++) {
2025-02-23 21:26:22 -05:00
draw_card(p->hand.cards[i], cards_texture_fun(g), g->black_card_backs);
}
for (int i = 0; i < p->scored.count; i++) {
2025-02-23 21:26:22 -05:00
draw_card(p->scored.cards[i], cards_texture_fun(g), g->black_card_backs);
}
}
void draw_cards(Game *g) {
draw_player_cards(g, &g->player);
draw_player_cards(g, &g->right);
draw_player_cards(g, &g->left);
for (int i = 0; i < g->field.count; i++) {
2025-02-23 21:26:22 -05:00
draw_card(g->field.cards[i], cards_texture_fun(g), g->black_card_backs);
2025-02-25 05:29:50 -05:00
}
for (int i = 0; i < g->deck.count; i++) {
2025-02-23 21:26:22 -05:00
draw_card(g->deck.cards[i], cards_texture_fun(g), g->black_card_backs);
}
}
2025-02-02 18:13:19 -05:00
void draw_frame(Game *g) {
BeginDrawing();
ClearBackground(RAYWHITE);
2025-02-23 21:26:22 -05:00
if (g->state == GAME_STATE_TITLE_SCREEN) {
title_draw(g);
EndDrawing();
return;
}
2025-02-23 21:26:22 -05:00
if (g->state == GAME_STATE_OPTIONS) {
options_draw(g);
2025-02-23 18:07:57 -05:00
EndDrawing();
return;
}
2025-02-25 05:29:50 -05:00
draw_cards(g);
2025-02-22 13:50:38 -05:00
if (g->state == GAME_STATE_DEALING) {
DrawText("Dealing....", 60, 385, 40, BLACK);
} else if (g->field_multiplier) {
DrawText(g->field_multiplier->name, 60, 385, 30, BLACK);
DrawText(g->field_multiplier->explanation, 60, 445, 20, BLACK);
}
2025-02-20 19:54:55 -05:00
2025-02-25 19:50:24 -05:00
DrawText(g->player.points_string, 1000, 650, 20, BLACK);
DrawText(g->player.teyaku_string, 1000, 680, 20, BLACK);
DrawText(g->right.points_string, 1000, 260, 20, BLACK);
DrawText(g->right.teyaku_string, 1100, 260, 20, BLACK);
DrawText(g->left.points_string, 200, 260, 20, BLACK);
DrawText(g->left.teyaku_string, 300, 260, 20, BLACK);
2025-02-16 20:50:13 -05:00
char round_text[40];
2025-02-22 14:36:31 -05:00
if (g->current_round < g->number_of_rounds)
sprintf(round_text, "Round %d / %d", g->current_round+1, g->number_of_rounds);
else
sprintf(round_text, "Game Over");
DrawText(round_text, 20, 875, 20, BLACK);
2025-02-19 06:04:10 -05:00
if (is_player_turn(g)) {
switch (g->state) {
case GAME_STATE_CHOOSING_FROM_HAND:
2025-02-22 13:44:18 -05:00
DrawText("Choose a card to play from your hand", 60, 485, 20, BLACK);
2025-02-19 06:04:10 -05:00
break;
case GAME_STATE_CHOOSING_TARGET:
DrawText("Choose a target on the field", 60, 485, 20, BLACK);
2025-02-25 05:55:03 -05:00
Card *selected_card = NULL;
for (int i = 0; i < g->player.hand.count; i++) {
if (g->player.hand.cards[i]->selected) {
selected_card = g->player.hand.cards[i];
break;
}
}
if (!selected_card) break;
bool draw_next_rec = true;
for (int i = 0; i < g->field.count; i++) {
if (g->field.cards[i]->month == selected_card->month) draw_next_rec = false;
}
if (draw_next_rec) DrawRectangleRec(next_card_position(&g->field), BLUE);
2025-02-19 06:04:10 -05:00
break;
default:
break;
}
2025-02-06 19:44:48 -05:00
}
2025-02-20 07:09:57 -05:00
if (g->dialog) {
dialog_draw(g->dialog);
}
2025-02-02 18:13:19 -05:00
EndDrawing();
}
void run_until_closing(Game *g) {
while (!WindowShouldClose() && !g->should_close) {
run_frame(g);
draw_frame(g);
}
}
void transfer_points(Game *g, int *to, int *from, int amount) {
amount *= g->field_multiplier->value;
*to += amount;
*from -= amount;
}
void transfer_kan(Game *g, int *to, int *from, int amount) {
amount *= g->kan_value;
transfer_points(g, to, from, amount);
}