diff --git a/card.c b/card.c index 37be84b..4f8983a 100644 --- a/card.c +++ b/card.c @@ -1,3 +1,5 @@ +#include +#include #include "card.h" static Vector2 card_size = (Vector2) { CARD_WIDTH, CARD_HEIGHT }; @@ -9,14 +11,19 @@ void draw_card(Card *c, Texture2D *cards_texture) { int pos_vert = i_vert * CARD_HEIGHT; int pos_horiz = i_horiz * CARD_WIDTH; - DrawTexturePro( - *cards_texture, - (Rectangle) { pos_horiz, pos_vert, CARD_WIDTH, CARD_HEIGHT }, - (Rectangle) { c->position.x, c->position.y, card_size.x, card_size.y }, - (Vector2) { 0, 0 }, - 0., - RAYWHITE - ); + if (c->visible) { + DrawTexturePro( + *cards_texture, + (Rectangle) { pos_horiz, pos_vert, CARD_WIDTH, CARD_HEIGHT }, + (Rectangle) { c->position.x, c->position.y, card_size.x, card_size.y }, + (Vector2) { 0, 0 }, + 0., + RAYWHITE + ); + } else { + DrawRectangleRec((Rectangle) { c->position.x, c->position.y, card_size.x, card_size.y }, BLACK); + } + if (c->selected) { DrawCircle(c->position.x + 10, c->position.y + 10, 10, BLUE); } @@ -26,3 +33,128 @@ bool point_within_card(Card *c, Vector2 point) { return point.x > c->position.x && point.x < c->position.x + card_size.x && point.y > c->position.y && point.y < c->position.y + card_size.y; } + +void shuffle_hand(Hand *h) { + Card *swap; + for (int i = h->count - 1; i >= 0; i--) { + int index = rand() % (i + 1); + swap = h->cards[i]; + h->cards[i] = h->cards[index]; + h->cards[index] = swap; + } +} + +void order_deck(Hand *h) { + Card *swap; + + swap = h->cards[1]; + h->cards[1] = h->cards[47-0]; + h->cards[47-0] = swap; + + swap = h->cards[5]; + h->cards[5] = h->cards[47-1]; + h->cards[47-1] = swap; + + swap = h->cards[9]; + h->cards[9] = h->cards[47-2]; + h->cards[47-2] = swap; + + swap = h->cards[0]; + h->cards[0] = h->cards[47-12]; + h->cards[47-12] = swap; + + swap = h->cards[4]; + h->cards[4] = h->cards[47-13]; + h->cards[47-13] = swap; + + swap = h->cards[8]; + h->cards[8] = h->cards[47-14]; + h->cards[47-14] = swap; +} + +void remove_from_hand(Hand *h, Card *c) { + bool card_found = false; + for (int i = 0; i < h->count - 1; i++) { + if (h->cards[i] == c) card_found = true; + if (card_found) h->cards[i] = h->cards[i + 1]; + } + h->count--; +} + +int first_open_spot(Hand *h) { + int order_guy[48]; + int order_guy_count = 0; + + for (int i = 0; i < 48; i++) { + order_guy[i] = 0; + } + + for (int i = 0; i < h->count; i++) { + if (h->cards[i] == NULL) continue; + order_guy[h->cards[i]->order] = 1; + order_guy_count++; + } + + int fos = h->count; + for (int i = 0; i < order_guy_count; i++) { + if (order_guy[i]) continue; + fos = i; + break; + } + return fos; +} + +void add_to_hand(Hand *h, Card *c, float deal_speed) { + c->order = first_open_spot(h); + h->cards[h->count] = c; + + c->move.position = &c->position; + c->move.origin.x = c->position.x; + c->move.origin.y = c->position.y; + switch (h->display_type) { + case HAND_DISPLAY_ROW: + c->move.destination.x = h->position.x + (c->order * (CARD_WIDTH + 10)); + c->move.destination.y = h->position.y; + break; + case HAND_DISPLAY_FIELD: + c->move.destination.x = h->position.x + ((c->order / 2) * (CARD_WIDTH + 10)); + c->move.destination.y = h->position.y + (c->order % 2 * (CARD_HEIGHT + 10)); + break; + case HAND_DISPLAY_DECK: + c->move.destination.x = h->position.x; + c->move.destination.y = h->position.y; + break; + case HAND_DISPLAY_SCORED: + c->move.destination.x = h->position.x + (c->order * (CARD_WIDTH - 30)); + c->move.destination.y = h->position.y; + break; + } + c->move.curve = CURVE_EASE_IN_OUT; + c->move.current_time = 0.; + c->move.end_time = deal_speed; + + h->count++; +} + +bool card_done_moving(Card *c) { + return c->move.current_time > c->move.end_time; +} + +void deal(Hand *from, Hand *to, int count, bool up, float deal_speed) { + for (int i = 0; i < count; i++) { + Card *c = from->cards[from->count - 1]; + c->visible = up; + add_to_hand(to, c, deal_speed); + from->count--; + } +} + +Rectangle next_card_position(Hand *h) { + int i = first_open_spot(h); + return (Rectangle) { + h->position.x + ((i / 2) * (CARD_WIDTH + 10)), + h->position.y + (i % 2 * (CARD_HEIGHT + 10)), + CARD_WIDTH, + CARD_HEIGHT + }; +} diff --git a/card.h b/card.h index e00e9b4..7e1844c 100644 --- a/card.h +++ b/card.h @@ -4,12 +4,20 @@ #include #include +#include "move.h" + typedef struct Card Card; typedef struct Hand Hand; #define CARD_WIDTH 73 #define CARD_HEIGHT 120 #define CARD_BORDER 5 +#define CRANE_INDEX 0 +#define CURTAIN_INDEX 8 +#define MOON_INDEX 28 +#define RAINY_MAN_INDEX 40 +#define PHOENIX_INDEX 44 + typedef enum CardType { CHAFF, RIBBON, @@ -46,14 +54,32 @@ struct Card { Month month; Vector2 position; bool selected; + bool visible; + Move move; + int order; }; +typedef enum HandDisplayType { + HAND_DISPLAY_ROW, + HAND_DISPLAY_FIELD, + HAND_DISPLAY_DECK, + HAND_DISPLAY_SCORED, +} HandDisplayType; + struct Hand { - Card cards[48]; + Card *cards[48]; int count; + Vector2 position; + HandDisplayType display_type; }; void draw_card(Card *c, Texture2D *cards_texture); bool point_within_card(Card *c, Vector2 v); +void shuffle_hand(Hand *h); +void deal(Hand *from, Hand *to, int count, bool up, float deal_speed); +void remove_from_hand(Hand *h, Card *c); +void add_to_hand(Hand *h, Card *c, float deal_speed); +bool card_done_moving(Card *c); +Rectangle next_card_position(Hand *h); #endif diff --git a/dekiyaku.c b/dekiyaku.c index 6475888..911b479 100644 --- a/dekiyaku.c +++ b/dekiyaku.c @@ -3,23 +3,23 @@ #include "dekiyaku.h" -void calculate_dekiyaku(const Hand h, Dekiyaku *d) { +void calculate_dekiyaku(Hand *h, Dekiyaku *d) { int brights = 0, ribbons = 0, ribbons_except_november = 0, blue_ribbons = 0, poetry_ribbons = 0, boar = 0, deer = 0, butterflies = 0; - for (int i = 0; i < h.count; i++) { - const Card card = h.cards[i]; - switch (card.type) { + for (int i = 0; i < h->count; i++) { + Card *card = h->cards[i]; + switch (card->type) { case BRIGHT: brights++; break; case RIBBON: ribbons++; - if (card.month != NOVEMBER) ribbons_except_november++; - switch (card.ribbon_type) { + if (card->month != NOVEMBER) ribbons_except_november++; + switch (card->ribbon_type) { case RIBBON_BLUE: blue_ribbons++; break; case RIBBON_POETRY: poetry_ribbons++; break; default: break; } break; case ANIMAL: - switch (card.month) { + switch (card->month) { case JUNE: butterflies++; break; case JULY: boar++; break; case OCTOBER: deer++; break; @@ -113,3 +113,11 @@ void dekiyaku_to_string(Dekiyaku *d, char *str) { strcat(str, meld_str); } } + +int dekiyaku_score(Dekiyaku *d) { + int score = 0; + for (int i = 0; i < d->count; i++) { + score += d->meld[i].value; + } + return score; +} diff --git a/dekiyaku.h b/dekiyaku.h index bfd0fa7..3d4566e 100644 --- a/dekiyaku.h +++ b/dekiyaku.h @@ -3,6 +3,13 @@ #include "card.h" +typedef enum DekiyakuAction { + DEKIYAKU_ACTION_NONE, + DEKIYAKU_ACTION_SAGE, + DEKIYAKU_ACTION_SHOUBU, + DEKIYAKU_ACTION_CANCEL, +} DekiyakuAction; + typedef enum DekiyakuMeldType { NONE, FIVE_BRIGHTS, @@ -23,8 +30,9 @@ typedef struct Dekiyaku { int count; } Dekiyaku; -void calculate_dekiyaku(const Hand h, Dekiyaku *d); +void calculate_dekiyaku(Hand *h, Dekiyaku *d); char *meld_name(DekiyakuMeldType d); void dekiyaku_to_string(Dekiyaku *d, char *str); +int dekiyaku_score(Dekiyaku *d); #endif diff --git a/dialog.c b/dialog.c new file mode 100644 index 0000000..60695bb --- /dev/null +++ b/dialog.c @@ -0,0 +1,260 @@ +#include +#include +#include +#include + +#include + +#include "dialog.h" + +Dialog dialogs[6]; + +void handle_click_cancel_yes(Game *g) { + g->player.dekiyaku_action = DEKIYAKU_ACTION_CANCEL; + g->state = GAME_STATE_CALCULATING_DEKIYAKU_SCORE; +} + +void handle_click_cancel_no(Game *g) { + g->state = GAME_STATE_CHOOSING_FROM_HAND; +} + +void handle_click_shoubu(Game *g) { + g->player.dekiyaku_action = DEKIYAKU_ACTION_SHOUBU; + g->state = GAME_STATE_CALCULATING_DEKIYAKU_SCORE; +} + +void handle_click_sage(Game *g) { + g->player.dekiyaku_action = DEKIYAKU_ACTION_SAGE; + g->turn_number++; + g->state = GAME_STATE_CHOOSING_FROM_HAND; +} + +void handle_click_ok_end_of_round(Game *g) { + g->state = GAME_STATE_END_OF_ROUND; +} + +void handle_click_ok_end_of_game(Game *g) { + g->state = GAME_STATE_TITLE_SCREEN; +} + +void handle_click_play_again(Game *g) { + g->state = GAME_STATE_NEW_GAME; +} + +void handle_click_quit(Game *g) { + g->should_close = true; +} + +void handle_click_claim_set_teyaku(Game *g) { + g->player.teyaku.chaff = CHAFF_TEYAKU_NONE; + teyaku_to_string(&g->player.teyaku, g->player.teyaku_string); + g->state = GAME_STATE_START_OF_TURN; +} + +void handle_click_claim_chaff_teyaku(Game *g) { + g->player.teyaku.set = SET_TEYAKU_NONE; + teyaku_to_string(&g->player.teyaku, g->player.teyaku_string); + g->state = GAME_STATE_START_OF_TURN; +} + +void handle_click_claim_both_teyaku(Game *g) { + g->state = GAME_STATE_START_OF_TURN; +} + +void handle_click_dont_claim_teyaku(Game *g) { + (&g->player.teyaku)->chaff = CHAFF_TEYAKU_NONE; + g->player.teyaku.set = SET_TEYAKU_NONE; + teyaku_to_string(&g->player.teyaku, g->player.teyaku_string); + g->state = GAME_STATE_START_OF_TURN; +} + +void init_dialogs(Game *g) { + Dialog *cancel_dialog = &dialogs[0]; + cancel_dialog->text_count = 1; + cancel_dialog->text[0] = malloc(200); + strcpy(cancel_dialog->text[0], "Would you like to cancel?"); + cancel_dialog->text_color = BLACK; + cancel_dialog->options_count = 2; + cancel_dialog->game = g; + + cancel_dialog->options[0].text = malloc(50); + strcpy(cancel_dialog->options[0].text, "Yes"); + cancel_dialog->options[0].color = GREEN; + cancel_dialog->options[0].handle = &handle_click_cancel_yes; + + cancel_dialog->options[1].text = malloc(50); + strcpy(cancel_dialog->options[1].text, "No"); + cancel_dialog->options[1].color = RED; + cancel_dialog->options[1].handle = &handle_click_cancel_no; + + Dialog *shoubu_dialog = &dialogs[1]; + shoubu_dialog->text_count = 1; + shoubu_dialog->text[0] = malloc(200); + strcpy(shoubu_dialog->text[0], "You have dekiyaku! Sage or shoubu?"); + shoubu_dialog->text_color = BLACK; + shoubu_dialog->options_count = 2; + shoubu_dialog->game = g; + + shoubu_dialog->options[0].text = malloc(50); + strcpy(shoubu_dialog->options[0].text, "Sage"); + shoubu_dialog->options[0].color = GREEN; + shoubu_dialog->options[0].handle = &handle_click_sage; + + shoubu_dialog->options[1].text = malloc(50); + strcpy(shoubu_dialog->options[1].text, "Shoubu"); + shoubu_dialog->options[1].color = RED; + shoubu_dialog->options[1].handle = &handle_click_shoubu; + + Dialog *no_dekiyaku_end_of_round_dialog = &dialogs[2]; + no_dekiyaku_end_of_round_dialog->text_count = 3; + no_dekiyaku_end_of_round_dialog->text[0] = malloc(200); + no_dekiyaku_end_of_round_dialog->text[1] = malloc(200); + no_dekiyaku_end_of_round_dialog->text[2] = malloc(200); + strcpy(no_dekiyaku_end_of_round_dialog->text[0], "Player score"); + strcpy(no_dekiyaku_end_of_round_dialog->text[1], "Right score"); + strcpy(no_dekiyaku_end_of_round_dialog->text[2], "Left score"); + no_dekiyaku_end_of_round_dialog->text_color = BLACK; + no_dekiyaku_end_of_round_dialog->options_count = 1; + no_dekiyaku_end_of_round_dialog->game = g; + + no_dekiyaku_end_of_round_dialog->options[0].text = malloc(50); + strcpy(no_dekiyaku_end_of_round_dialog->options[0].text, "Okay"); + no_dekiyaku_end_of_round_dialog->options[0].color = GREEN; + no_dekiyaku_end_of_round_dialog->options[0].handle = &handle_click_ok_end_of_round; + + Dialog *end_of_game_dialog = &dialogs[3]; + end_of_game_dialog->text_count = 4; + end_of_game_dialog->text[0] = malloc(200); + end_of_game_dialog->text[1] = malloc(200); + end_of_game_dialog->text[2] = malloc(200); + end_of_game_dialog->text[3] = malloc(200); + end_of_game_dialog->text_color = BLACK; + end_of_game_dialog->options_count = 2; + end_of_game_dialog->game = g; + + end_of_game_dialog->options[0].text = malloc(50); + strcpy(end_of_game_dialog->options[0].text, "Play Again"); + end_of_game_dialog->options[0].color = GREEN; + end_of_game_dialog->options[0].handle = &handle_click_play_again; + end_of_game_dialog->options[1].text = malloc(50); + strcpy(end_of_game_dialog->options[1].text, "Quit"); + end_of_game_dialog->options[1].color = GREEN; + end_of_game_dialog->options[1].handle = &handle_click_quit; + + Dialog *dekiyaku_end_of_round_dialog = &dialogs[4]; + dekiyaku_end_of_round_dialog->text_count = 3; + dekiyaku_end_of_round_dialog->text[0] = malloc(200); + dekiyaku_end_of_round_dialog->text[1] = malloc(200); + dekiyaku_end_of_round_dialog->text[2] = malloc(200); + strcpy(dekiyaku_end_of_round_dialog->text[0], "Player score"); + strcpy(dekiyaku_end_of_round_dialog->text[1], "Right score"); + strcpy(dekiyaku_end_of_round_dialog->text[2], "Left score"); + dekiyaku_end_of_round_dialog->text_color = BLACK; + dekiyaku_end_of_round_dialog->options_count = 1; + dekiyaku_end_of_round_dialog->game = g; + + dekiyaku_end_of_round_dialog->options[0].text = malloc(50); + strcpy(dekiyaku_end_of_round_dialog->options[0].text, "Okay"); + dekiyaku_end_of_round_dialog->options[0].color = GREEN; + dekiyaku_end_of_round_dialog->options[0].handle = &handle_click_ok_end_of_round; + + Dialog *teyaku_dialog = &dialogs[5]; + teyaku_dialog->text_count = 3; + teyaku_dialog->text[0] = malloc(200); + teyaku_dialog->text[1] = malloc(200); + teyaku_dialog->text[2] = malloc(200); + strcpy(teyaku_dialog->text[0], "You can claim some teyaku"); + teyaku_dialog->text_color = BLACK; + teyaku_dialog->options_count = 4; + teyaku_dialog->game = g; + + teyaku_dialog->options[0].text = malloc(50); + strcpy(teyaku_dialog->options[0].text, "Claim Set Teyaku"); + teyaku_dialog->options[0].color = SKYBLUE; + teyaku_dialog->options[0].handle = &handle_click_claim_set_teyaku; + + teyaku_dialog->options[1].text = malloc(50); + strcpy(teyaku_dialog->options[1].text, "Claim Chaff Teyaku"); + teyaku_dialog->options[1].color = SKYBLUE; + teyaku_dialog->options[1].handle = &handle_click_claim_chaff_teyaku; + + teyaku_dialog->options[2].text = malloc(50); + strcpy(teyaku_dialog->options[2].text, "Claim Both Teyaku"); + teyaku_dialog->options[2].color = GREEN; + teyaku_dialog->options[2].handle = &handle_click_claim_both_teyaku; + + teyaku_dialog->options[3].text = malloc(50); + strcpy(teyaku_dialog->options[3].text, "Don't Claim"); + teyaku_dialog->options[3].color = RED; + teyaku_dialog->options[3].handle = &handle_click_dont_claim_teyaku; +} + +void cancel_dialog(Game *g) { g->dialog = &dialogs[0]; } +void shoubu_dialog(Game *g) { g->dialog = &dialogs[1]; } +void no_dekiyaku_end_of_round_dialog(Game *g) { g->dialog = &dialogs[2]; } +void end_of_game_dialog(Game *g) { g->dialog = &dialogs[3]; } +void dekiyaku_end_of_round_dialog(Game *g) { g->dialog = &dialogs[4]; } +void teyaku_dialog(Game *g) { g->dialog = &dialogs[5]; } + +Rectangle dialog_option_outer_rectangle(Dialog *d, int i) { + if (d->options_count < 4) { + return (Rectangle) { + ((960 * (i + 1)) / (d->options_count + 1)) - 10 + 200, + 500, + MeasureText(d->options[i].text, DIALOG_OPTION_FONT_SIZE) + 20, + 40, + }; + } else { + return (Rectangle) { + ((960 * (i % 2)) / ((d->options_count / 2) + 1)) - 10 + 250, + 500 + ((i / 2) * 50), + MeasureText(d->options[i].text, DIALOG_OPTION_FONT_SIZE) + 20, + 40, + }; + } +} + +Rectangle dialog_option_inner_rectangle(Dialog *d, int i) { + Rectangle outer = dialog_option_outer_rectangle(d, i); + return (Rectangle) { + outer.x + 3, + outer.y + 3, + outer.width - 6, + outer.height - 6, + }; +} + +void dialog_draw(Dialog *d) { + int text_width; + DrawRectangleRec(DIALOG_OUTER_RECTANGLE, BLACK); + DrawRectangleRec(DIALOG_INNER_RECTANGLE, WHITE); + + for (int i = 0; i < d->text_count; i++) { + text_width = MeasureText(d->text[i], DIALOG_TEXT_FONT_SIZE); + DrawText(d->text[i], 700 - (text_width / 2), 300 + (70 * (i - 1)), DIALOG_TEXT_FONT_SIZE, d->text_color); + } + + for (int i = 0; i < d->options_count; i++) { + DialogOption *o = &d->options[i]; + Rectangle outer = dialog_option_outer_rectangle(d, i); + Rectangle inner = dialog_option_inner_rectangle(d, i); + DrawRectangleRec(outer, BLACK); + DrawRectangleRec(inner, o->color); + DrawText(o->text, inner.x + 3, inner.y + 3, DIALOG_OPTION_FONT_SIZE, BLACK); + } +} + +void dialog_handle_input(Dialog *d) { + if (IsMouseButtonPressed(0)) { + Vector2 mouse_pos = GetMousePosition(); + for (int i = 0; i < d->options_count; i++) { + DialogOption *o = &d->options[i]; + Rectangle outer = dialog_option_outer_rectangle(d, i); + if (CheckCollisionPointRec(mouse_pos, outer)) { + o->handle(d->game); + d->game->dialog = NULL; + break; + } + } + } +} diff --git a/dialog.h b/dialog.h new file mode 100644 index 0000000..ea75d32 --- /dev/null +++ b/dialog.h @@ -0,0 +1,39 @@ +#ifndef _HF_DIALOG_ +#define _HF_DIALOG_ + +typedef struct DialogOption DialogOption; +typedef struct Dialog Dialog; + +#include "game.h" + +#define DIALOG_OUTER_RECTANGLE (Rectangle) { 200, 200, 1000, 500 } +#define DIALOG_INNER_RECTANGLE (Rectangle) { 220, 220, 960, 460 } +#define DIALOG_TEXT_FONT_SIZE 60 +#define DIALOG_OPTION_FONT_SIZE 30 + +struct DialogOption { + char *text; + Color color; + void (*handle) (Game *g); +}; + +struct Dialog { + char *text[8]; + int text_count; + Color text_color; + DialogOption options[4]; + int options_count; + Game *game; +}; + +void init_dialogs(Game *g); +void cancel_dialog(Game *g); +void shoubu_dialog(Game *g); +void no_dekiyaku_end_of_round_dialog(Game *g); +void end_of_game_dialog(Game *g); +void dekiyaku_end_of_round_dialog(Game *g); +void teyaku_dialog(Game *g); +void dialog_draw(Dialog *d); +void dialog_handle_input(Dialog *d); + +#endif diff --git a/field_multiplier.c b/field_multiplier.c new file mode 100644 index 0000000..c9e91eb --- /dev/null +++ b/field_multiplier.c @@ -0,0 +1,21 @@ +#include + +#include "field_multiplier.h" +#include "card.h" + +static FieldMultiplier small_field = { "Small Field", "", 1 }; +static FieldMultiplier large_field = { "Large Field", "Score transfers are doubled", 2 }; +static FieldMultiplier grand_field = { "Grand Field", "Score transfers are quadrupled" ,4 }; + +FieldMultiplier *calculate_field_multiplier(Hand *h) { + bool large = false; + for (int i = 0; i < h->count; i++) { + Card *c = h->cards[i]; + if (c->index == CRANE_INDEX || c->index == CURTAIN_INDEX || c->index == MOON_INDEX) + large = true; + else if (c->index == RAINY_MAN_INDEX || c->index == PHOENIX_INDEX) + return &grand_field; + } + + return large ? &large_field : &small_field; +} diff --git a/field_multiplier.h b/field_multiplier.h new file mode 100644 index 0000000..82e0ec6 --- /dev/null +++ b/field_multiplier.h @@ -0,0 +1,16 @@ +#ifndef _HF_FIELD_MULTIPLIER_ +#define _HF_FIELD_MULTIPLIER_ + +typedef struct FieldMultiplier FieldMultiplier; + +#include "card.h" + +struct FieldMultiplier { + char *name; + char *explanation; + int value; +}; + +FieldMultiplier *calculate_field_multiplier(Hand *h); + +#endif diff --git a/game.c b/game.c new file mode 100644 index 0000000..7bd6a93 --- /dev/null +++ b/game.c @@ -0,0 +1,932 @@ +#include +#include +#include + +#include "game.h" +#include "card.h" +#include "teyaku.h" +#include "dekiyaku.h" +#include "field_multiplier.h" +#include "special_cases.h" +#include "play.h" +#include "dialog.h" + +Vector2 mouse_pos; +char teyaku_calculation[400]; + +void initialize_game(Game *g) { + Image cards_image = LoadImage("img/cards.png"); + g->cards_texture = LoadTextureFromImage(cards_image); + UnloadImage(cards_image); + + g->deck.count = 0; + g->deck.position = (Vector2) { 500, 300 }; + g->deck.display_type = HAND_DISPLAY_DECK; + g->should_close = false; + g->state = GAME_STATE_INITIALIZING; + g->field_multiplier = NULL; + g->dialog = NULL; + + g->kan_value = 12; + g->number_of_rounds = 1; + g->black_card_backs = true; + g->deal_speed = 0.2; + g->options = malloc(sizeof(Options)); + load_options_from_game(g); + + init_dialogs(g); + + 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; + } + g->cards[i] = (Card) { i, t, rt, month, { 500, 300 }, false }; + g->cards[i].move.end_time = 0.; + g->cards[i].move.position = &g->cards[i].position; + g->cards[i].move.destination = (Vector2) { 500, 300 }; + g->cards[i].order = i; + g->cards[i].selected = false; + } + + 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'; + g->player.teyaku_string[0] = '\0'; + g->right.teyaku_string[0] = '\0'; + g->left.teyaku_string[0] = '\0'; + 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; + + g->player.teyaku.calculated = false; + g->right.teyaku.calculated = false; + g->left.teyaku.calculated = false; + + 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; + + g->field.position = (Vector2) { 600, 300 }; + g->field.display_type = HAND_DISPLAY_FIELD; + + 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_OPTIONS; +} + +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; + } + + return NULL; +} + +bool is_player_turn(Game *g) { + return current_player(g) == &g->player; +} + +void handle_input(Game *g) { + if (g->state == GAME_STATE_OPTIONS) { + return options_handle_input(g); + } + + if (g->dialog) { + return dialog_handle_input(g->dialog); + } + + if (IsKeyPressed(KEY_R)) { + g->state = GAME_STATE_INITIALIZING; + return; + } + + if (!is_player_turn(g)) return; + + switch (g->state) { + case GAME_STATE_CHOOSING_FROM_HAND: + 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)) { + for (int j = 0; j < 48; j++) { + g->cards[j].selected = false; + } + g->player.hand.cards[i]->selected = true; + break; + } + } + } + break; + case GAME_STATE_CHOOSING_TARGET: + 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; + } else { + for (int j = 0; j < g->player.hand.count; j++) { + g->player.hand.cards[j]->selected = false; + } + g->player.hand.cards[i]->selected = true; + } + } + } + + 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; + } + } + + 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])) { + g->current_play_from_hand = selected_card; + g->current_play_target = g->field.cards[i]; + } else { + printf("Invalid\n"); + } + break; + } + } + + if (CheckCollisionPointRec(mouse_pos, next_card_position(&g->field))) { + if (valid_play(&g->field, selected_card, NULL)) { + g->current_play_from_hand = selected_card; + g->current_play_target = NULL; + } else { + printf("Invalid\n"); + } + } + } + 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; + default: + break; + } +} + +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); + add_to_hand(scored, played, g->deal_speed); + + if (same_month_card_count == 3) { + for (int i = 0; i < 3; i++) { + remove_from_hand(&g->field, same_month_card[i]); + add_to_hand(scored, same_month_card[i], g->deal_speed); + } + } else { + remove_from_hand(&g->field, target); + add_to_hand(scored, target, g->deal_speed); + } +} + +void run_frame_ai_playing(Game *g) { + Hand *hand = ¤t_player(g)->hand; + Hand *scored = ¤t_player(g)->scored; + + Play play = ai_play(hand, &g->field); + play.played->visible = true; + + if (play.target) { + capture_card_from_field(g, play.played, play.target, hand, scored); + } else { + remove_from_hand(hand, play.played); + add_to_hand(&g->field, play.played, g->deal_speed); + } +} + +void run_frame_initializing(Game *g) { + g->turn_number = g->dealer->seat; + + 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, ""); + + g->current_play_from_hand = NULL; + g->current_play_target = NULL; + + g->deck.count = 0; + for (int i = 0; i < 48; i++) { + Card *c = &g->cards[i]; + c->visible = false; + add_to_hand(&g->deck, c, g->deal_speed); + } + + shuffle_hand(&g->deck); + // order_deck(&g->deck); + + 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); + g->state = GAME_STATE_DEALING; +} + +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; +} + +void run_frame_dealing(Game *g) { + if (current_player(g)->hand.count < 4) { + deal(&g->deck, ¤t_player(g)->hand, 4, is_player_turn(g), g->deal_speed); + g->turn_number++; + } else if (g->field.count < 3) { + deal(&g->deck, &g->field, 3, true, g->deal_speed); + } else if (current_player(g)->hand.count < 7) { + deal(&g->deck, ¤t_player(g)->hand, 3, is_player_turn(g), g->deal_speed); + g->turn_number++; + } else if (g->field.count < 6) { + deal(&g->deck, &g->field, 3, true, g->deal_speed); + } else { + if (misdeal(g)) { + printf("misdeal\n"); + g->state = GAME_STATE_INITIALIZING; + } else { + g->turn_number++; + g->state = GAME_STATE_CALCULATING_FIELD_MULTIPLIER; + } + } +} + +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); + teyaku_to_string(&g->player.teyaku, g->player.teyaku_string); + calculate_teyaku(g->right.hand, &g->right.teyaku); + teyaku_to_string(&g->right.teyaku, g->right.teyaku_string); + calculate_teyaku(g->left.hand, &g->left.teyaku); + 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; + } +} + +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) { + g->state = GAME_STATE_CALCULATING_SCORES; + } 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)) { + cancel_dialog(g); + } else { + // TODO: the AI might want to cancel at some point + g->state = GAME_STATE_CHOOSING_FROM_HAND; + } + } else { + g->state = GAME_STATE_CHOOSING_FROM_HAND; + } +} + +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) { + g->state = GAME_STATE_CHOOSING_TARGET; + return; + } + } +} + +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; + } +} + +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; + } + + bool no_cards_selected = true; + for (int i = 0; i < g->player.hand.count; i++) { + if (g->player.hand.cards[i]->selected) { + no_cards_selected = false; + break; + } + } + + if (no_cards_selected) { + g->state = GAME_STATE_CHOOSING_FROM_HAND; + return; + } + + if (g->current_play_from_hand) { + if (g->current_play_target) { + g->current_play_from_hand->selected = false; + + capture_card_from_field( + g, + g->current_play_from_hand, + g->current_play_target, + &g->player.hand, + &g->player.scored + ); + + + 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); + add_to_hand(&g->field, g->current_play_from_hand, g->deal_speed); + g->current_play_from_hand = NULL; + } + g->state = GAME_STATE_SHOWING_CARD_FROM_DECK; + } +} + +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); + add_to_hand(&g->deck, top_card, g->deal_speed); + top_card->visible = true; + top_card->move.end_time = 0.3; + top_card->move.destination.y = top_card->move.destination.y + 150; + g->state = GAME_STATE_PLAYING_FROM_DECK; +} + +void run_frame_playing_from_deck(Game *g) { + Hand *to_hand = ¤t_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); + g->state = GAME_STATE_CHECKING_FOR_NEW_DEKIYAKU; + } else if(target_count == 0) { + remove_from_hand(&g->deck, top_card); + 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 = ¤t_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)) { + if (g->current_play_target) { + 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; + } +} + +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; + if (is_player_turn(g)) { + shoubu_dialog(g); + } else { + // TODO: better AI + cp->dekiyaku_action = DEKIYAKU_ACTION_SHOUBU; + g->state = GAME_STATE_CALCULATING_DEKIYAKU_SCORE; + } + } else { + g->state = GAME_STATE_START_OF_TURN; + } +} + +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; + } else { + shoubu_dialog(g); + } + } else { + // TODO: better AI + current_player(g)->dekiyaku_action = DEKIYAKU_ACTION_SHOUBU; + g->state = GAME_STATE_CALCULATING_DEKIYAKU_SCORE; + } +} + +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); +} + +void run_frame_calculating_scores(Game *g) { + no_dekiyaku_end_of_round_dialog(g); + + int hp[3]; + hp[0] = hand_points(&g->player.scored); + hp[1] = hand_points(&g->right.scored); + hp[2] = hand_points(&g->left.scored); + + SpecialCase special_case = calculate_special_case(g); + switch(special_case.type) { + case SPECIAL_CASE_ALL_EIGHTS: + sprintf(g->dialog->text[0], "All eights!"); + sprintf(g->dialog->text[0], "Dealer gets %d kan", special_case.score); + 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); + break; + case SPECIAL_CASE_DOUBLE_EIGHTS: + case SPECIAL_CASE_SIXTEEN_CHAFF: + if (special_case.type == SPECIAL_CASE_DOUBLE_EIGHTS) sprintf(g->dialog->text[0], "Double eights!"); + else sprintf(g->dialog->text[0], "Sixteen chaff!"); + 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); + 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); + 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); + g->dealer = &g->left; + break; + } + break; + default: + 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]); + 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); + break; + } +} + +void calculate_dekiyaku_score(Game *g, Player *p) { + 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)); + } 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); + } + } +} + +void run_frame_calculating_dekiyaku_score(Game *g) { + dekiyaku_end_of_round_dialog(g); + 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 { + // TODO: Hands are exhausted + } + pay_teyaku(g); +} + +void run_frame_end_of_round(Game *g) { + 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); + g->current_round++; + if (g->current_round >= g->number_of_rounds) { + g->state = GAME_STATE_END_OF_GAME; + } else { + g->state = GAME_STATE_INITIALIZING; + } +} + +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); +} + +void run_frame_end_of_game(Game *g) { + end_of_game_dialog(g); + 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; +} + +void move_cards(Game *g) { + float delta = GetFrameTime(); + for (int i = 0; i < 48; i++) { + move_position(&g->cards[i].move, delta); + } +} + +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); + if (!done_moving(g)) return; + + switch (g->state) { + case GAME_STATE_INITIALIZING: + run_frame_initializing(g); + return; + case GAME_STATE_DEALING: + run_frame_dealing(g); + break; + case GAME_STATE_CALCULATING_FIELD_MULTIPLIER: + run_frame_calculating_field_multiplier(g); + break; + case GAME_STATE_CALCULATING_TEYAKU: + run_frame_calculating_teyaku(g); + break; + case GAME_STATE_START_OF_TURN: + run_frame_start_of_turn(g); + break; + case GAME_STATE_CHECKING_FOR_CANCEL: + run_frame_checking_for_cancel(g); + break; + case GAME_STATE_CHOOSING_FROM_HAND: + run_frame_choosing_from_hand(g); + break; + case GAME_STATE_CHOOSING_TARGET: + run_frame_choosing_target(g); + break; + case GAME_STATE_SHOWING_CARD_FROM_DECK: + run_frame_showing_card_from_deck(g); + break; + case GAME_STATE_PLAYING_FROM_DECK: + run_frame_playing_from_deck(g); + break; + case GAME_STATE_CHOOSING_TARGET_FROM_DECK: + run_frame_choosing_target_from_deck(g); + break; + case GAME_STATE_CHECKING_FOR_NEW_DEKIYAKU: + run_frame_checking_for_new_dekiyaku(g); + break; + case GAME_STATE_SELECTING_DEKIYAKU_ACTION: + run_frame_selecting_dekiyaku_action(g); + break; + case GAME_STATE_CALCULATING_SCORES: + run_frame_calculating_scores(g); + break; + case GAME_STATE_CALCULATING_DEKIYAKU_SCORE: + run_frame_calculating_dekiyaku_score(g); + break; + 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; + case GAME_STATE_NEW_GAME: + run_frame_new_game(g); + break; + case GAME_STATE_OPTIONS: + break; + } +} + +void draw_player_cards(Game *g, Player *p) { + for (int i = 0; i < p->hand.count; i++) { + draw_card(p->hand.cards[i], &g->cards_texture); + } + + for (int i = 0; i < p->scored.count; i++) { + draw_card(p->scored.cards[i], &g->cards_texture); + } +} + +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++) { + draw_card(g->field.cards[i], &g->cards_texture); + } + + for (int i = 0; i < g->deck.count; i++) { + draw_card(g->deck.cards[i], &g->cards_texture); + } +} + +void draw_frame(Game *g) { + BeginDrawing(); + ClearBackground(RAYWHITE); + + draw_cards(g); + + if (g->state == GAME_STATE_OPTIONS) { + options_draw(g); + EndDrawing(); + return; + } + + 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); + } + + DrawText(g->player.points_string, 40, 650, 20, BLACK); + DrawText(g->player.teyaku_string, 40, 680, 20, BLACK); + DrawText(g->right.points_string, 40, 725, 20, BLACK); + DrawText(g->right.teyaku_string, 40, 755, 20, BLACK); + DrawText(g->left.points_string, 40, 800, 20, BLACK); + DrawText(g->left.teyaku_string, 40, 830, 20, BLACK); + + char round_text[40]; + 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); + + if (is_player_turn(g)) { + switch (g->state) { + case GAME_STATE_CHOOSING_FROM_HAND: + DrawText("Choose a card to play from your hand", 60, 485, 20, BLACK); + break; + case GAME_STATE_CHOOSING_TARGET: + DrawText("Choose a target on the field", 60, 485, 20, BLACK); + DrawRectangleRec(next_card_position(&g->field), BLUE); + break; + default: + break; + } + } + + if (g->dialog) { + dialog_draw(g->dialog); + } + + 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); +} diff --git a/game.h b/game.h new file mode 100644 index 0000000..8e14999 --- /dev/null +++ b/game.h @@ -0,0 +1,66 @@ +#ifndef _HF_GAME_ +#define _HF_GAME_ + +#include + +typedef struct Game Game; + +#include "card.h" +#include "field_multiplier.h" +#include "teyaku.h" +#include "dekiyaku.h" +#include "points.h" +#include "player.h" +#include "dialog.h" +#include "options.h" + +typedef enum GameState { + GAME_STATE_INITIALIZING, + GAME_STATE_DEALING, + GAME_STATE_CALCULATING_FIELD_MULTIPLIER, + GAME_STATE_CALCULATING_TEYAKU, + GAME_STATE_START_OF_TURN, + GAME_STATE_CHECKING_FOR_CANCEL, + GAME_STATE_CHOOSING_FROM_HAND, + GAME_STATE_CHOOSING_TARGET, + GAME_STATE_SHOWING_CARD_FROM_DECK, + GAME_STATE_PLAYING_FROM_DECK, + GAME_STATE_CHOOSING_TARGET_FROM_DECK, + GAME_STATE_CHECKING_FOR_NEW_DEKIYAKU, + GAME_STATE_SELECTING_DEKIYAKU_ACTION, + GAME_STATE_CALCULATING_SCORES, + GAME_STATE_CALCULATING_DEKIYAKU_SCORE, + GAME_STATE_END_OF_ROUND, + GAME_STATE_END_OF_GAME, + GAME_STATE_NEW_GAME, + GAME_STATE_TITLE_SCREEN, + GAME_STATE_OPTIONS, +} GameState; + +struct Game { + GameState state; + bool should_close; + Card cards[48]; + Texture2D cards_texture; + Hand deck, field; + FieldMultiplier *field_multiplier; + Card *current_play_from_hand, *current_play_target; + Player player, right, left; + int temp_points; + int turn_number; + Dialog *dialog; + Player *dealer; + int current_round; + int kan_value; + int number_of_rounds; + bool black_card_backs; + float deal_speed; + Options *options; +}; + +void initialize_game(Game *g); +void run_until_closing(Game *g); +void transfer_points(Game *g, int *to, int *from, int amount); +void transfer_kan(Game *g, int *to, int *from, int amount); + +#endif diff --git a/main.c b/main.c index a5d6ab1..69404e0 100644 --- a/main.c +++ b/main.c @@ -2,132 +2,25 @@ #include #include #include +#include #include -#include "card.h" -#include "move.h" -#include "teyaku.h" -#include "dekiyaku.h" +#include "game.h" char *text = "こんにちわ、 世界!"; +Texture2D cards_texture; int main(int argc, char** argv) { - InitWindow(900, 600, "Hanafuda Hachi-Hachi"); - SetTargetFPS(60); + srand(time(NULL)); + InitWindow(1400, 900, "Hanafuda Hachi-Hachi"); + SetTargetFPS(60); - Image cards_image = LoadImage("img/cards.png"); - Texture2D cards_texture = LoadTextureFromImage(cards_image); - UnloadImage(cards_image); + Game g; + initialize_game(&g); - /* - Hand h; - h.cards[0] = (Card) { 1, BRIGHT, RIBBON_NONE, NOVEMBER, (Vector2) { 0, 0 } }; - h.cards[1] = (Card) { 1, ANIMAL, RIBBON_NONE, NOVEMBER, (Vector2) { 0, 0 } }; - h.cards[2] = (Card) { 1, RIBBON, RIBBON_PLAIN, NOVEMBER, (Vector2) { 0, 0 } }; - h.cards[3] = (Card) { 1, CHAFF, RIBBON_NONE, NOVEMBER, (Vector2) { 0, 0 } }; - h.cards[4] = (Card) { 1, CHAFF, RIBBON_NONE, DECEMBER, (Vector2) { 0, 0 } }; - h.cards[5] = (Card) { 1, CHAFF, RIBBON_NONE, DECEMBER, (Vector2) { 0, 0 } }; - h.cards[6] = (Card) { 1, CHAFF, RIBBON_NONE, DECEMBER, (Vector2) { 0, 0 } }; - h.count = 7; - - printf("Teyaku: %d\n", calculate_teyaku(h)); - */ - - Card cards[48]; - 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; - } - cards[i] = (Card) { i, t, rt, month, (Vector2) { month * 75, (i % 4) * 123 }, false }; - } - - // float delta; - Vector2 mouse_pos; - char teyaku_calculation[400]; - strcpy(teyaku_calculation, ""); - char dekiyaku_calculation[400]; - strcpy(dekiyaku_calculation, ""); - - while (!WindowShouldClose()) { - // delta = GetFrameTime(); - - if (IsMouseButtonPressed(0)) { - mouse_pos = GetMousePosition(); - for (int i = 0; i < 48; i++) { - if (point_within_card(&cards[i], mouse_pos)) { - cards[i].selected = !cards[i].selected; - strcpy(teyaku_calculation, ""); - break; - } - } - } - - BeginDrawing(); - ClearBackground(RAYWHITE); - int num_selected = 0; - for (int i = 0; i < 48; i++) { - num_selected += cards[i].selected; - draw_card(&cards[i], &cards_texture); - } - - if (strlen(teyaku_calculation) == 0) { - Hand h; - h.count = 0; - for (int i = 0; i < 48; i++) { - if (cards[i].selected) memcpy(&h.cards[h.count++], &cards[i], sizeof(Card)); - } - - if (num_selected == 7) { - Teyaku t; - calculate_teyaku(h, &t); - teyaku_to_string(&t, teyaku_calculation); - } else { - strcpy(teyaku_calculation, ""); - } - - Dekiyaku d; - calculate_dekiyaku(h, &d); - dekiyaku_to_string(&d, dekiyaku_calculation); - } - - DrawText(teyaku_calculation, 10, 550, 25, BLACK); - DrawText(dekiyaku_calculation, 10, 500, 25, BLACK); - EndDrawing(); - } + run_until_closing(&g); - CloseWindow(); - - return 0; + CloseWindow(); + return 0; } diff --git a/move.c b/move.c index 7eed111..6aefbec 100644 --- a/move.c +++ b/move.c @@ -1,25 +1,32 @@ #include #include "move.h" -Vector2 move_position(Move *m, float delta) { +void move_position(Move *m, float delta) { m->current_time += delta; float percentage = m->current_time / m->end_time; if (percentage < 0.0) percentage = 0.0; else if (percentage > 1.0) percentage = 1.0; + Vector2 v; switch (m->curve) { case CURVE_LINEAR: - return (Vector2) { + v = (Vector2) { ((m->destination.x - m->origin.x) * percentage) + m->origin.x, ((m->destination.y - m->origin.y) * percentage) + m->origin.y }; + break; case CURVE_EASE_IN_OUT: percentage = -(cos(PI * percentage) - 1) / 2; - return (Vector2) { + v = (Vector2) { ((m->destination.x - m->origin.x) * percentage) + m->origin.x, ((m->destination.y - m->origin.y) * percentage) + m->origin.y }; + break; default: - return m->destination; + v = m->destination; + break; } + + m->position->x = v.x; + m->position->y = v.y; } diff --git a/move.h b/move.h index 298efd8..a791342 100644 --- a/move.h +++ b/move.h @@ -11,6 +11,7 @@ typedef enum Curve { typedef struct Move Move; struct Move { + Vector2 *position; Vector2 origin; Vector2 destination; Curve curve; @@ -18,6 +19,6 @@ struct Move { float end_time; }; -Vector2 move_position(Move *m, float delta); +void move_position(Move *m, float delta); #endif diff --git a/options.c b/options.c new file mode 100644 index 0000000..0cecc2a --- /dev/null +++ b/options.c @@ -0,0 +1,129 @@ +#include "options.h" + +int kan_value_from_index(int index) { return index == 0 ? 10 : 12; } +int index_from_kan_value(int kan_value) { return kan_value == 10 ? 0 : 1; } +int number_of_rounds_from_index(int index) { + int r[] = { 1, 3, 6, 12 }; + return r[index]; +} +int index_from_number_of_rounds(int number_of_rounds) { + int r[] = { 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 3 }; + return r[number_of_rounds]; +} +float deal_speed_from_index(int index) { return (index + 1) / 10. ; } +float index_from_deal_speed(float deal_speed) { return (int) (deal_speed * 10) - 1 ; } + +void load_options_from_game(Game *g) { + g->options->kan_value = index_from_kan_value(g->kan_value); + g->options->number_of_rounds = index_from_number_of_rounds(g->number_of_rounds); + g->options->card_backs = g->black_card_backs; + g->options->deal_speed = index_from_deal_speed(g->deal_speed); +} + +void save_options_to_game(Game *g) { + g->kan_value = kan_value_from_index(g->options->kan_value); + g->number_of_rounds = number_of_rounds_from_index(g->options->number_of_rounds); + g->black_card_backs = g->options->card_backs; + g->deal_speed = deal_speed_from_index(g->options->deal_speed); +} + +void handle_options_save(Game *g) { + save_options_to_game(g); + g->state = GAME_STATE_INITIALIZING; +} + +void handle_options_cancel(Game *g) { + load_options_from_game(g); + g->state = GAME_STATE_INITIALIZING; +} + +void handle_select_kan(Game *g, int index) { + g->options->kan_value = index; +} + +void handle_select_number_of_rounds(Game *g, int index) { + g->options->number_of_rounds = index; +} + +void handle_select_card_backs(Game *g, int index) { + g->options->card_backs = index; +} + +void handle_select_deal_speed(Game *g, int index) { + g->options->deal_speed = index; +} + +OptionsChoices kan_choices = { { "Ten", "Twelve" }, 2, 250, &handle_select_kan }; +OptionsChoices number_of_rounds_choices = { { "One", "Three", "Six", "Twelve" }, 4, 400, &handle_select_number_of_rounds }; +OptionsChoices card_backs_choices = { { "Red", "Black" }, 2, 550, &handle_select_card_backs }; +OptionsChoices deal_speed_choices = { { "Very Fast", "Fast", "Medium", "Slow", "Very Slow" }, 5, 700, &handle_select_deal_speed }; +OptionsChoices *oc[4] = { &kan_choices, &number_of_rounds_choices, &card_backs_choices, &deal_speed_choices }; + +void options_handle_input(Game *g) { + if (!IsMouseButtonPressed(0)) return; + + int left = 250; + int width = 900; + + Vector2 mouse_pos = GetMousePosition(); + int x = mouse_pos.x, y = mouse_pos.y; + for (int i = 0; i < 4; i++) { + OptionsChoices *choices = oc[i]; + if (y < choices->y || y > choices->y + 30) continue; + + for (int j = 0; j < choices->count; j++) { + char *choice = choices->choice[j]; + int w = MeasureText(choice, 30); + int center = left + (width / (choices->count + 1)) * (j + 1); + int min = center - (w/2); + int max = center + (w/2); + if (x > min && x < max) { + choices->handle(g, j); + return; + } + } + } + + if (x > 400 && x < 400 + MeasureText("Save", 30) + 12 && y > 800 && y < 836) { + handle_options_save(g); + return; + } + if (x > 900 && x < 900 + MeasureText("Cancel", 30) + 12 && y > 800 && y < 836) { + handle_options_cancel(g); + return; + } +} + +void DrawTextCentered(char *text, int center_x, int y, int point, Color color) { + int width = MeasureText(text, point); + DrawText(text, center_x - (width / 2), y, point, color); +} + +void draw_option_choices(OptionsChoices *choices, int selected_index) { + int left = 250; + int width = 900; + for (int i = 0; i < choices->count; i++) { + Color color = i == selected_index ? RED : BLACK; + DrawTextCentered(choices->choice[i], left + (width / (choices->count + 1)) * (i + 1), choices->y, 30, color); + } +} + +void options_draw(Game *g) { + DrawTextCentered("Options", 700, 50, 60, BLACK); + DrawTextCentered("Kan Value", 700, 175, 40, BLACK); + draw_option_choices(&kan_choices, g->options->kan_value); + DrawTextCentered("Number of Rounds", 700, 325, 40, BLACK); + draw_option_choices(&number_of_rounds_choices, g->options->number_of_rounds); + DrawTextCentered("Card Backs", 700, 475, 40, BLACK); + draw_option_choices(&card_backs_choices, g->options->card_backs); + DrawTextCentered("Deal Speed", 700, 625, 40, BLACK); + draw_option_choices(&deal_speed_choices, g->options->deal_speed); + + DrawRectangle(400, 800, MeasureText("Save", 30) + 12, 36, BLACK); + DrawRectangle(403, 803, MeasureText("Save", 30) + 6, 30, GREEN); + DrawText("Save", 406, 806, 30, BLACK); + + DrawRectangle(900, 800, MeasureText("Cancel", 30) + 12, 36, BLACK); + DrawRectangle(903, 803, MeasureText("Cancel", 30) + 6, 30, RED); + DrawText("Cancel", 906, 806, 30, BLACK); +} diff --git a/options.h b/options.h new file mode 100644 index 0000000..52f4a79 --- /dev/null +++ b/options.h @@ -0,0 +1,28 @@ +#ifndef _HF_OPTIONS_ +#define _HF_OPTIONS_ + +typedef struct Options Options; +typedef struct OptionsChoices OptionsChoices; + +#include "game.h" + +struct Options { + int kan_value; + int number_of_rounds; + int card_backs; + int deal_speed; +}; + +struct OptionsChoices { + char *choice[5]; + int count; + int y; + void (*handle)(Game*, int); +}; + +void load_options_from_game(Game *g); +void save_options_to_game(Game *g); +void options_handle_input(Game *g); +void options_draw(Game *g); + +#endif diff --git a/play.c b/play.c new file mode 100644 index 0000000..a93464d --- /dev/null +++ b/play.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include "play.h" + +bool valid_play(Hand *field, Card *played, Card *target) { + if (target == NULL) { + bool matching_month_in_field = false; + for (int i = 0; i < field->count; i++) { + if (field->cards[i]->month == played->month) { + matching_month_in_field = true; + break; + } + } + return !matching_month_in_field; + } else { + bool target_in_field = false; + for (int i = 0; i < field->count; i++) { + if (field->cards[i]->index == target->index) { + target_in_field = true; + break; + } + } + if (!target_in_field) return false; + return played->month == target->month; + } +} + +void valid_targets(Card *active, Hand *field, Card **targets, int *target_count) { + *target_count = 0; + for (int i = 0; i < field->count; i++) { + if (field->cards[i]->month == active->month) { + targets[(*target_count)++] = field->cards[i]; + } + } + targets[*target_count] = NULL; // Sentinel +} + +Card *valid_target(Card *active, Hand *field) { + for (int i = 0; i < field->count; i++) { + if (field->cards[i]->month == active->month) { + return field->cards[i]; + } + } + + return NULL; +} + +Play ai_play(Hand *hand, Hand *field) { + // very naive play initially + Card *played = hand->cards[0]; + for (int i = 0; i < field->count; i++) { + Card *target = field->cards[i]; + if (valid_play(field, played, target)) return (Play) { played, target }; + } + return (Play) { played, NULL }; +} diff --git a/play.h b/play.h new file mode 100644 index 0000000..4aaf5ef --- /dev/null +++ b/play.h @@ -0,0 +1,20 @@ +#ifndef _HF_PLAY_ +#define _HF_PLAY_ + +#include + +typedef struct Play Play; + +#include "card.h" + +struct Play { + Card *played; + Card *target; +}; + +bool valid_play(Hand *field, Card *played, Card *target); +void valid_targets(Card *active, Hand *field, Card **targets, int *target_count); +Card *valid_target(Card *active, Hand *field); +Play ai_play(Hand *hand, Hand *field); + +#endif diff --git a/player.h b/player.h new file mode 100644 index 0000000..a3212c3 --- /dev/null +++ b/player.h @@ -0,0 +1,28 @@ +#ifndef _HF_PLAYER_ +#define _HF_PLAYER_ + +typedef struct Player Player; +typedef enum PlayerSeat { + PLAYER, + RIGHT, + LEFT +} PlayerSeat; + +#include "card.h" + +struct Player { + PlayerSeat seat; + char *name; + Hand hand, scored; + Teyaku teyaku; + Dekiyaku dekiyaku; + DekiyakuAction dekiyaku_action; + int dekiyaku_score; + int points; + char points_string[20]; + char teyaku_string[50]; + bool ai; + bool dealer; +}; + +#endif diff --git a/points.c b/points.c new file mode 100644 index 0000000..fa6185e --- /dev/null +++ b/points.c @@ -0,0 +1,30 @@ +#include + +#include "points.h" + +int hand_points(Hand *hand) { + int points = 0; + for (int i = 0; i < hand->count; i++) { + Card *c = hand->cards[i]; + switch (c->type) { + case CHAFF: + points += 1; + break; + case RIBBON: + points += 5; + break; + case ANIMAL: + points += 10; + break; + case BRIGHT: + points += 20; + break; + } + } + + return points - 88; +} + +void kan_points_string(Game *g, int points, char *string) { + sprintf(string, "%d kan %d", points / g->kan_value, points % g->kan_value); +} diff --git a/points.h b/points.h new file mode 100644 index 0000000..7f97b12 --- /dev/null +++ b/points.h @@ -0,0 +1,10 @@ +#ifndef _HF_POINTS_ +#define _HF_POINTS_ + +#include "card.h" +#include "game.h" + +int hand_points(Hand *hand); +void kan_points_string(Game *g, int points, char *string); + +#endif diff --git a/special_cases.c b/special_cases.c new file mode 100644 index 0000000..819d0b2 --- /dev/null +++ b/special_cases.c @@ -0,0 +1,48 @@ +#include "game.h" +#include "card.h" +#include "points.h" +#include "special_cases.h" + +int hand_count_chaff(Hand *hand) { + int chaff = 0; + for (int i = 0; i < hand->count; i++) { + Card *c = hand->cards[i]; + if (c->type == CHAFF || c->month == NOVEMBER) chaff++; + } + return chaff; +} + +SpecialCase calculate_special_case(Game *g) { + int player_points = hand_points(&g->player.scored); + int right_points = hand_points(&g->right.scored); + int left_points = hand_points(&g->left.scored); + + if (player_points == 0 && + right_points == 0 && + left_points == 0) { + return (SpecialCase) { SPECIAL_CASE_ALL_EIGHTS, SPECIAL_CASE_TARGET_DEALER, 10 }; + } + + if (player_points >= 80) + return (SpecialCase) { SPECIAL_CASE_DOUBLE_EIGHTS, SPECIAL_CASE_TARGET_PLAYER, player_points - 70 }; + if (right_points >= 80) + return (SpecialCase) { SPECIAL_CASE_DOUBLE_EIGHTS, SPECIAL_CASE_TARGET_RIGHT, right_points - 70 }; + if (left_points >= 80) + + return (SpecialCase) { SPECIAL_CASE_DOUBLE_EIGHTS, SPECIAL_CASE_TARGET_LEFT, left_points - 70 }; + + int player_chaff = hand_count_chaff(&g->player.scored); + if (player_chaff >= 16) + return (SpecialCase) { SPECIAL_CASE_SIXTEEN_CHAFF, SPECIAL_CASE_TARGET_PLAYER, (2 * player_chaff) - 20 }; + + int right_chaff = hand_count_chaff(&g->right.scored); + if (right_chaff >= 16) + return (SpecialCase) { SPECIAL_CASE_SIXTEEN_CHAFF, SPECIAL_CASE_TARGET_RIGHT, (2 * right_chaff) - 20 }; + + int left_chaff = hand_count_chaff(&g->left.scored); + if (left_chaff >= 16) + return (SpecialCase) { SPECIAL_CASE_SIXTEEN_CHAFF, SPECIAL_CASE_TARGET_LEFT, (2 * left_chaff) - 20 }; + + return (SpecialCase) { SPECIAL_CASE_NONE, SPECIAL_CASE_TARGET_NONE, 0 }; +} + diff --git a/special_cases.h b/special_cases.h new file mode 100644 index 0000000..ed89dfe --- /dev/null +++ b/special_cases.h @@ -0,0 +1,30 @@ +#ifndef _HF_SPECIAL_CASES_ +#define _HF_SPECIAL_CASES_ + +typedef struct SpecialCase SpecialCase; +typedef enum SpecialCaseType { + SPECIAL_CASE_NONE, + SPECIAL_CASE_ALL_EIGHTS, + SPECIAL_CASE_DOUBLE_EIGHTS, + SPECIAL_CASE_SIXTEEN_CHAFF, +} SpecialCaseType; + +typedef enum SpecialCaseTarget { + SPECIAL_CASE_TARGET_NONE, + SPECIAL_CASE_TARGET_DEALER, + SPECIAL_CASE_TARGET_PLAYER, + SPECIAL_CASE_TARGET_RIGHT, + SPECIAL_CASE_TARGET_LEFT, +} SpecialCaseTarget; + +#include "game.h" + +struct SpecialCase { + SpecialCaseType type; + SpecialCaseTarget target; + int score; +}; + +SpecialCase calculate_special_case(Game *g); + +#endif diff --git a/teyaku.c b/teyaku.c index 8066304..d4e397b 100644 --- a/teyaku.c +++ b/teyaku.c @@ -7,7 +7,7 @@ SetTeyaku calculate_set_teyaku(const Hand h) { int month_counts[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int month_stands[12] = { 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1 }; for (int i = 0; i < h.count; i++) { - Card c = h.cards[i]; + Card c = *h.cards[i]; month_counts[c.month]++; } @@ -45,7 +45,7 @@ ChaffTeyaku calculate_chaff_teyaku(const Hand h) { int chaff = 0; for (int i = 0; i < h.count; i++) { - Card c = h.cards[i]; + Card c = *h.cards[i]; if (c.month == NOVEMBER) chaff++; // November cards are all counted as chaff here else if (c.type == BRIGHT) brights++; else if (c.type == RIBBON) ribbons++; @@ -75,6 +75,10 @@ int chaff_teyaku_points(ChaffTeyaku ct) { return chaff_teyaku_points_array[ct]; } +int teyaku_points(Teyaku *t) { + return set_teyaku_points(t->set) + chaff_teyaku_points(t->chaff); +} + char *set_teyaku_english(SetTeyaku st) { return set_teyaku_english_array[st]; } @@ -86,10 +90,21 @@ char *chaff_teyaku_english(ChaffTeyaku ct) { void calculate_teyaku(const Hand h, Teyaku *t) { t->chaff = calculate_chaff_teyaku(h); t->set = calculate_set_teyaku(h); + t->calculated = true; +} + +void set_teyaku_to_string(Teyaku *t, char *str) { + sprintf(str, "Set: %s(%d)", set_teyaku_english(t->set), set_teyaku_points(t->set)); +} + +void chaff_teyaku_to_string(Teyaku *t, char *str) { + sprintf(str, "Chaff: %s(%d)", chaff_teyaku_english(t->chaff), chaff_teyaku_points(t->chaff)); } void teyaku_to_string(Teyaku *t, char *str) { int set_points = set_teyaku_points(t->set); int chaff_points = chaff_teyaku_points(t->chaff); - sprintf(str, "Set: %s(%d) / Chaff: %s(%d) / Total: %d", set_teyaku_english(t->set), set_points, chaff_teyaku_english(t->chaff), chaff_points, set_points + chaff_points); + // sprintf(str, "Set: %s(%d) / Chaff: %s(%d) / Total: %d", set_teyaku_english(t->set), set_points, chaff_teyaku_english(t->chaff), chaff_points, set_points + chaff_points); + if (set_points + chaff_points > 0) sprintf(str, "Teyaku: %d", set_points + chaff_points); + else sprintf(str, ""); } diff --git a/teyaku.h b/teyaku.h index 6e99abc..20d4e3c 100644 --- a/teyaku.h +++ b/teyaku.h @@ -1,6 +1,8 @@ #ifndef _HF_TEYAKU_ #define _HF_TEYAKU_ +#include + #include "card.h" typedef enum SetTeyaku { @@ -29,9 +31,13 @@ typedef enum ChaffTeyaku { typedef struct Teyaku { ChaffTeyaku chaff; SetTeyaku set; + bool calculated; } Teyaku; +int teyaku_points(Teyaku *t); void calculate_teyaku(const Hand h, Teyaku *t); +void set_teyaku_to_string(Teyaku *t, char *str); +void chaff_teyaku_to_string(Teyaku *t, char *str); void teyaku_to_string(Teyaku *t, char *str); #endif