Merge branch 'main' of http://whatguns.ddns.net:3000/bassguitarbill/hanafuda into main
This commit is contained in:
commit
f4d2dd954b
148
card.c
148
card.c
@ -1,3 +1,5 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#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
|
||||
};
|
||||
}
|
||||
|
28
card.h
28
card.h
@ -4,12 +4,20 @@
|
||||
#include <stdbool.h>
|
||||
#include <raylib.h>
|
||||
|
||||
#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
|
||||
|
22
dekiyaku.c
22
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;
|
||||
}
|
||||
|
10
dekiyaku.h
10
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
|
||||
|
260
dialog.c
Normal file
260
dialog.c
Normal file
@ -0,0 +1,260 @@
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <raylib.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
dialog.h
Normal file
39
dialog.h
Normal file
@ -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
|
21
field_multiplier.c
Normal file
21
field_multiplier.c
Normal file
@ -0,0 +1,21 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#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;
|
||||
}
|
16
field_multiplier.h
Normal file
16
field_multiplier.h
Normal file
@ -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
|
932
game.c
Normal file
932
game.c
Normal file
@ -0,0 +1,932 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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);
|
||||
}
|
66
game.h
Normal file
66
game.h
Normal file
@ -0,0 +1,66 @@
|
||||
#ifndef _HF_GAME_
|
||||
#define _HF_GAME_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
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
|
129
main.c
129
main.c
@ -2,132 +2,25 @@
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <raylib.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
15
move.c
15
move.c
@ -1,25 +1,32 @@
|
||||
#include <math.h>
|
||||
#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;
|
||||
}
|
||||
|
3
move.h
3
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
|
||||
|
129
options.c
Normal file
129
options.c
Normal file
@ -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);
|
||||
}
|
28
options.h
Normal file
28
options.h
Normal file
@ -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
|
57
play.c
Normal file
57
play.c
Normal file
@ -0,0 +1,57 @@
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#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 };
|
||||
}
|
20
play.h
Normal file
20
play.h
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef _HF_PLAY_
|
||||
#define _HF_PLAY_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
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
|
28
player.h
Normal file
28
player.h
Normal file
@ -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
|
30
points.c
Normal file
30
points.c
Normal file
@ -0,0 +1,30 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#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);
|
||||
}
|
10
points.h
Normal file
10
points.h
Normal file
@ -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
|
48
special_cases.c
Normal file
48
special_cases.c
Normal file
@ -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 };
|
||||
}
|
||||
|
30
special_cases.h
Normal file
30
special_cases.h
Normal file
@ -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
|
21
teyaku.c
21
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, "");
|
||||
}
|
||||
|
6
teyaku.h
6
teyaku.h
@ -1,6 +1,8 @@
|
||||
#ifndef _HF_TEYAKU_
|
||||
#define _HF_TEYAKU_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
||||
|
Loading…
Reference in New Issue
Block a user