484 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			484 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include <string.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"
 | |
| 
 | |
| Vector2 mouse_pos;
 | |
| char teyaku_calculation[400];
 | |
| 
 | |
| void initialize_game(Game *g) {
 | |
|   g->deck.count = 0;
 | |
|   g->deck.position = (Vector2) { 800, 400 };
 | |
|   g->deck.display_type = HAND_DISPLAY_DECK;
 | |
|   g->should_close = false;
 | |
|   g->state = GAME_STATE_INITIALIZING;
 | |
|   g->field_multiplier = NULL;
 | |
|   g->player.teyaku.calculated = false;
 | |
|   g->left.teyaku.calculated = false;
 | |
|   g->right.teyaku.calculated = false;
 | |
|   g->kan_value = 12;
 | |
|   g->current_play_from_hand = NULL;
 | |
|   g->current_play_target = NULL;
 | |
| 
 | |
|   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, { 800, 100 }, false };
 | |
|     g->cards[i].move.end_time = 0.;
 | |
|     g->cards[i].move.position = &g->cards[i].position;
 | |
|     g->cards[i].move.destination = (Vector2) { 800, 400 };
 | |
|     g->cards[i].order = i;
 | |
|     g->deck.cards[i] = &g->cards[i];
 | |
|     g->deck.count++;
 | |
|   }
 | |
| 
 | |
|   shuffle_hand(&g->deck);
 | |
| 
 | |
|   g->player.points = 10 * g->kan_value;
 | |
|   g->right.points = 10 * g->kan_value;
 | |
|   g->left.points = 10 * g->kan_value;
 | |
|   g->player.points_string[0] = '\0';
 | |
|   g->right.points_string[0] = '\0';
 | |
|   g->left.points_string[0] = '\0';
 | |
| 
 | |
|   g->player.hand.count = 0;
 | |
|   g->player.hand.position = (Vector2) { 300, 600 };
 | |
|   g->player.hand.display_type = HAND_DISPLAY_ROW;
 | |
|   g->right.hand.count = 0;
 | |
|   g->right.hand.position = (Vector2) { 750, 125 };
 | |
|   g->right.hand.display_type = HAND_DISPLAY_ROW;
 | |
|   g->left.hand.count = 0;
 | |
|   g->left.hand.position = (Vector2) { 50, 125 };
 | |
|   g->left.hand.display_type = HAND_DISPLAY_ROW;
 | |
|   g->field.count = 0;
 | |
|   g->field.position = (Vector2) { 400, 300 };
 | |
|   g->field.display_type = HAND_DISPLAY_FIELD;
 | |
|   g->player.scored.count = 0;
 | |
|   g->player.scored.position = (Vector2) { 300, 750 };
 | |
|   g->player.scored.display_type = HAND_DISPLAY_SCORED;
 | |
|   g->right.scored.count = 0;
 | |
|   g->right.scored.position = (Vector2) { 750, 25 };
 | |
|   g->right.scored.display_type = HAND_DISPLAY_SCORED;
 | |
|   g->left.scored.count = 0;
 | |
|   g->left.scored.position = (Vector2) { 50, 25 };
 | |
|   g->left.scored.display_type = HAND_DISPLAY_SCORED;
 | |
| 
 | |
|   strcpy(teyaku_calculation, "");
 | |
| 
 | |
|   Image cards_image = LoadImage("img/cards.png");
 | |
|   g->cards_texture = LoadTextureFromImage(cards_image);
 | |
|   UnloadImage(cards_image);
 | |
|   g->state = GAME_STATE_DEALING;
 | |
| }
 | |
| 
 | |
| bool stale_calculation = true;
 | |
| void handle_input(Game *g) {
 | |
|   switch (g->state) {
 | |
|   case GAME_STATE_PLAYER_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_PLAYER_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;
 | |
|   default:
 | |
|     break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool run_frame_from_deck(Game *g, Hand *to_hand) {
 | |
|   Card *top_card = g->deck.cards[g->deck.count - 1];
 | |
|   if (top_card->visible) {
 | |
|     Card *target = valid_target(top_card, &g->field);
 | |
|     if (target) {
 | |
|       remove_from_hand(&g->field, target);
 | |
|       add_to_hand(to_hand, target);
 | |
|       remove_from_hand(&g->deck, top_card);
 | |
|       add_to_hand(to_hand, top_card);
 | |
|     } else {
 | |
|       remove_from_hand(&g->deck, top_card);
 | |
|       add_to_hand(&g->field, top_card);
 | |
|     }
 | |
|     return true;
 | |
|   } else {
 | |
|     remove_from_hand(&g->deck, top_card);
 | |
|     add_to_hand(&g->deck, top_card);
 | |
|     top_card->visible = true;
 | |
|     top_card->move.end_time = 0.3;
 | |
|     top_card->move.destination.x = top_card->move.destination.x + 100;
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void run_frame_ai_playing(Game *g, Hand *hand, Hand *scored) {
 | |
|   Play play = ai_play(hand, &g->field);
 | |
|   play.played->visible = true;
 | |
| 
 | |
|   if (play.target) {
 | |
|     remove_from_hand(hand, play.played);
 | |
|     add_to_hand(scored, play.played);
 | |
|     remove_from_hand(&g->field, play.target);
 | |
|     add_to_hand(scored, play.target);
 | |
|   } else {
 | |
|     remove_from_hand(hand, play.played);
 | |
|     add_to_hand(&g->field, play.played);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void run_frame_dealing(Game *g) {
 | |
|   // TODO maybe we only need these once per game
 | |
|   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);
 | |
| 
 | |
|   if (g->player.hand.count < 4) {
 | |
|     deal(&g->deck, &g->player.hand, 4, true);
 | |
|   } else if (g->left.hand.count < 4) {
 | |
|     deal(&g->deck, &g->left.hand, 4, false);
 | |
|   } else if (g->right.hand.count < 4) {
 | |
|     deal(&g->deck, &g->right.hand, 4, false);
 | |
|   } else if (g->field.count < 3) {
 | |
|     deal(&g->deck, &g->field, 3, true);
 | |
|   } else if (g->player.hand.count < 7) {
 | |
|     deal(&g->deck, &g->player.hand, 3, true);
 | |
|   } else if (g->left.hand.count < 7) {
 | |
|     deal(&g->deck, &g->left.hand, 3, false);
 | |
|   } else if (g->right.hand.count < 7) {
 | |
|     deal(&g->deck, &g->right.hand, 3, false);
 | |
|   } else if (g->field.count < 6) {
 | |
|     deal(&g->deck, &g->field, 3, true);
 | |
|   } else {
 | |
|     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);
 | |
|   for (int i = 0; i < 48; i++) {
 | |
|     g->cards[i].selected = false;
 | |
|   }
 | |
|   g->state = GAME_STATE_PLAYER_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_PLAYER_CHOOSING_TARGET;
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void run_frame_player_choosing_target(Game *g) {
 | |
|   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_PLAYER_CHOOSING_FROM_HAND;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (g->current_play_from_hand) {
 | |
|     if (g->current_play_target) {
 | |
|       g->current_play_from_hand->selected = false;
 | |
|       remove_from_hand(&g->player.hand, g->current_play_from_hand);
 | |
|       add_to_hand(&g->player.scored, g->current_play_from_hand);
 | |
|       remove_from_hand(&g->field, g->current_play_target);
 | |
|       add_to_hand(&g->player.scored, g->current_play_target);
 | |
|       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->current_play_from_hand = NULL;
 | |
|     }
 | |
|     g->state = GAME_STATE_PLAYER_FROM_DECK;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void run_frame_player_from_deck(Game *g) {
 | |
|   if (run_frame_from_deck(g, &g->player.scored)) {
 | |
|     calculate_dekiyaku(&g->player.scored, &g->player.dekiyaku);
 | |
|     if (dekiyaku_score(&g->player.dekiyaku) != g->player.dekiyaku_score) {
 | |
|       g->player.dekiyaku_score = dekiyaku_score(&g->player.dekiyaku);
 | |
|       g->state = GAME_STATE_PLAYER_HAS_DEKIYAKU;
 | |
|     } else {
 | |
|       g->state = GAME_STATE_RIGHT_PLAYING;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void run_frame_player_has_dekiyaku(Game *g) {
 | |
|   if (g->player.dekiyaku_action == DEKIYAKU_ACTION_SAGE) {
 | |
|     g->state = GAME_STATE_RIGHT_PLAYING;
 | |
|   } else if (g->player.dekiyaku_action == DEKIYAKU_ACTION_SHOUBU) {
 | |
|     g->state = GAME_STATE_CALCULATING_DEKIYAKU_SCORE;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void run_frame_right_playing(Game *g) {
 | |
|   run_frame_ai_playing(g, &g->right.hand, &g->right.scored);
 | |
|   g->state = GAME_STATE_RIGHT_FROM_DECK;
 | |
| }
 | |
| 
 | |
| void run_frame_right_from_deck(Game *g) {
 | |
|   if (run_frame_from_deck(g, &g->right.scored)) {
 | |
|     calculate_dekiyaku(&g->right.scored, &g->right.dekiyaku);
 | |
|     g->state = GAME_STATE_LEFT_PLAYING;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void run_frame_left_playing(Game *g) {
 | |
|   run_frame_ai_playing(g, &g->left.hand, &g->left.scored);
 | |
|   g->state = GAME_STATE_LEFT_FROM_DECK;
 | |
| }
 | |
| 
 | |
| void run_frame_left_from_deck(Game *g) {
 | |
|   if (run_frame_from_deck(g, &g->left.scored)) {
 | |
|     if (g->player.hand.count) {
 | |
|       g->state = GAME_STATE_PLAYER_CHOOSING_FROM_HAND;
 | |
|     } else {
 | |
|       g->state = GAME_STATE_CALCULATING_SCORES;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void run_frame_calculating_scores(Game *g) {
 | |
|   printf("Hand scores: %d %d %d\n", hand_points(&g->player.scored), hand_points(&g->right.scored), hand_points(&g->left.scored));
 | |
| 
 | |
|   SpecialCase special_case = calculate_special_case(g);
 | |
|   switch(special_case.type) {
 | |
|   case SPECIAL_CASE_ALL_EIGHTS:
 | |
|     printf("All eights! Dealer gets %d kan\n", special_case.score);
 | |
|     break;
 | |
|   case SPECIAL_CASE_DOUBLE_EIGHTS:
 | |
|   case SPECIAL_CASE_SIXTEEN_CHAFF:
 | |
|     printf("Double eights or 16 chaff! 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);
 | |
|       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);
 | |
|       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);
 | |
|       break;
 | |
|     }
 | |
|     break;
 | |
|   default:
 | |
|     transfer_points(g, &g->player.points, &g->temp_points, hand_points(&g->player.scored));
 | |
|     transfer_points(g, &g->right.points, &g->temp_points, hand_points(&g->right.scored));
 | |
|     transfer_points(g, &g->left.points, &g->temp_points, hand_points(&g->left.scored));
 | |
|     break;
 | |
|   }
 | |
|   g->state = GAME_STATE_INITIALIZING;
 | |
| }
 | |
| 
 | |
| void run_frame(Game *g) {
 | |
|   handle_input(g);
 | |
| 
 | |
|   float delta = GetFrameTime();
 | |
|   bool done_moving = true;
 | |
|   for (int i = 0; i < 48; i++) {
 | |
|     move_position(&g->cards[i].move, delta);
 | |
|     if (!card_done_moving(&g->cards[i])) done_moving = false;
 | |
|   }
 | |
|   if (!done_moving) return;
 | |
| 
 | |
|   switch (g->state) {
 | |
|   case GAME_STATE_INITIALIZING:
 | |
|     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_PLAYER_CHOOSING_FROM_HAND:
 | |
|     run_frame_player_choosing_from_hand(g);
 | |
|     break;
 | |
|   case GAME_STATE_PLAYER_CHOOSING_TARGET:
 | |
|     run_frame_player_choosing_target(g);
 | |
|     break;
 | |
|   case GAME_STATE_PLAYER_FROM_DECK:
 | |
|     run_frame_player_from_deck(g);
 | |
|     break;
 | |
|   case GAME_STATE_RIGHT_PLAYING:
 | |
|     run_frame_right_playing(g);
 | |
|     break;
 | |
|   case GAME_STATE_RIGHT_FROM_DECK:
 | |
|     run_frame_right_from_deck(g);
 | |
|     break;
 | |
|   case GAME_STATE_LEFT_PLAYING:
 | |
|     run_frame_left_playing(g);
 | |
|     break;
 | |
|   case GAME_STATE_LEFT_FROM_DECK:
 | |
|     run_frame_left_from_deck(g);
 | |
|     break;
 | |
|   case GAME_STATE_CALCULATING_SCORES:
 | |
|     run_frame_calculating_scores(g);
 | |
|     break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void draw_frame(Game *g) {
 | |
|   BeginDrawing();
 | |
|   ClearBackground(RAYWHITE);
 | |
|   for (int i = 0; i < 48; i++) {
 | |
|     draw_card(&g->cards[i], &g->cards_texture);
 | |
|   }
 | |
| 
 | |
|   if (g->field_multiplier) DrawText(g->field_multiplier->name, 60, 385, 40, BLACK);
 | |
|   if (g->player.teyaku.calculated) {
 | |
|     char s[200];
 | |
|     teyaku_to_string(&g->player.teyaku, s);
 | |
|     DrawText(s, 5, 25, 30, BLACK);
 | |
|   }
 | |
| 
 | |
|   DrawText(g->player.points_string, 40, 700, 20, BLACK);
 | |
|   DrawText(g->right.points_string, 40, 750, 20, BLACK);
 | |
|   DrawText(g->left.points_string, 40, 800, 20, BLACK);
 | |
| 
 | |
|   switch (g->state) {
 | |
|   case GAME_STATE_PLAYER_CHOOSING_FROM_HAND:
 | |
|     DrawText("Choose a card to play", 60, 485, 20, BLACK);
 | |
|     break;
 | |
|   case GAME_STATE_PLAYER_CHOOSING_TARGET:
 | |
|     DrawText("Choose a target on the field", 60, 485, 20, BLACK);
 | |
|     DrawRectangleRec(next_card_position(&g->field), BLUE);
 | |
|     break;
 | |
|   default:
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   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);
 | |
| }
 |