/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2006 Will Robertson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ /* TODO list: - improve AI - buy/use nukes - DONE - build farms/factories */ #include "plugin.h" #include "lib/display_text.h" #include "lib/playback_control.h" #include "lib/pluginlib_actions.h" #include "pluginbitmaps/superdom_boarditems.h" char buf[255]; /* key mappings */ #define SUPERDOM_OK PLA_SELECT #define SUPERDOM_CANCEL PLA_CANCEL #define SUPERDOM_RIGHT PLA_RIGHT #define SUPERDOM_LEFT PLA_LEFT #define SUPERDOM_UP PLA_UP #define SUPERDOM_DOWN PLA_DOWN #define SUPERDOM_RIGHT_REPEAT PLA_RIGHT_REPEAT #define SUPERDOM_LEFT_REPEAT PLA_LEFT_REPEAT #define SUPERDOM_UP_REPEAT PLA_UP_REPEAT #define SUPERDOM_DOWN_REPEAT PLA_DOWN_REPEAT /***** game settings *****/ /* Some defines for the prices */ #define PRICE_MEN 1 #define PRICE_MOVE 100 #define PRICE_TANK 300 #define PRICE_PLANE 600 #define PRICE_FARM 1150 #define PRICE_FACTORY 1300 #define PRICE_NUKE 2000 #define STRINGIZE_2(X) #X #define STRINGIZE(X) STRINGIZE_2(X) #define PRICE_MEN_STR STRINGIZE(PRICE_MEN) #define PRICE_MOVE_STR STRINGIZE(PRICE_MOVE) #define PRICE_TANK_STR STRINGIZE(PRICE_TANK) #define PRICE_PLANE_STR STRINGIZE(PRICE_PLANE) #define PRICE_FARM_STR STRINGIZE(PRICE_FARM) #define PRICE_FACTORY_STR STRINGIZE(PRICE_FACTORY) #define PRICE_NUKE_STR STRINGIZE(PRICE_NUKE) /* surrender thresholds */ #define HUMAN_SURRENDER_THRESHOLD 15 #define COMPUTER_SURRENDER_THRESHOLD 15 #define COMPUTER_HARD_SURRENDER_THRESHOLD 25 /* AI settings */ #define AI_INVESTING_LEVEL 2 #define AI_BUILD_NUKES_LEVEL 3 #define AI_BUILD_INDS_FARMS_LEVEL 2 /* board size */ #define BOARD_SIZE 10 #define NUM_SPACES (BOARD_SIZE*BOARD_SIZE) #define COLOUR_DARK 0 #define COLOUR_LIGHT 1 /* drawing presets */ #define MARGIN 5 #if (LCD_DEPTH >= 16) #define MY_BITMAP_PART rb->lcd_bitmap_transparent_part #else #define MY_BITMAP_PART rb->lcd_mono_bitmap_part #endif #if LCD_WIDTH > LCD_HEIGHT #define BOX_WIDTH ((LCD_WIDTH-(MARGIN*2))/BOARD_SIZE) #define BOX_HEIGHT ((BOX_WIDTH*2)/3) #else #define BOX_HEIGHT ((LCD_HEIGHT-(MARGIN*2)-15)/BOARD_SIZE) #define BOX_WIDTH ((BOX_HEIGHT*2)/3) #endif /* NUM_BOX HEIGHT and WIDTH are used for the number pad in the game. The height * calculation includes spacing for the text placed above and below the number * pad (it divides by 6 instead of just 4). The width calculation gives extra * spacing on the sides of the pad too (divides by 5 instead of 3). */ #define NUM_BOX_HEIGHT (LCD_HEIGHT/6) #define NUM_BOX_WIDTH (LCD_WIDTH/5) #define NUM_MARGIN_X (LCD_WIDTH-3*NUM_BOX_WIDTH)/2 #define NUM_MARGIN_Y (LCD_HEIGHT-4*NUM_BOX_HEIGHT)/2 /* These parameters define the piece image dimensions, Stride is the total width * of the bitmap. */ #define ICON_STRIDE STRIDE(SCREEN_MAIN, BMPWIDTH_superdom_boarditems, BMPHEIGHT_superdom_boarditems) #define ICON_HEIGHT (BMPHEIGHT_superdom_boarditems/6) #define ICON_WIDTH (BMPWIDTH_superdom_boarditems/2) enum { RET_VAL_OK, RET_VAL_USB, RET_VAL_QUIT_ERR, /* quit or error */ }; static void gen_interest(void); static void init_resources(void); static int select_square(void); static void update_score(void); static void gen_resources(void); static void draw_cursor(void); static void draw_board(void); struct tile { signed int colour; /* -1 = Unset */ bool tank; bool plane; bool nuke; bool ind; bool farm; int men; }; struct resources { int cash; int food; int farms; int inds; int men; int tanks; int planes; int nukes; int bank; int moves; }; static struct settings { int compstartfarms; int compstartinds; int humanstartfarms; int humanstartinds; int startcash; int startfood; int movesperturn; /* 1=easy 2=medium 3=hard */ /* AI difficulty works like this: easy: - no movement - no investing - will build factories if it has none medium: - movement - investing - can build factories/farms if it has money hard: - can buy/use nukes - will hold out longer (surrender threshold 25) */ int compdiff; bool spoil_enabled; bool persistent_units; } superdom_settings; static struct resources humanres; static struct resources compres; enum { GS_PROD, GS_MOVE, GS_WAR }; static int gamestate; static struct cursor { int x; int y; } cursor; static struct tile board[BOARD_SIZE+2][BOARD_SIZE+2]; static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; static void init_board(void) { int i,j; rb->srand(*rb->current_tick); for(i=0;i<12;i++) { /* Hopefully about 50% each colour */ for(j=0;jBOARD_SIZE)||(j>BOARD_SIZE)) board[i][j].colour = -1; /* Unset */ else board[i][j].colour = rb->rand()%2; board[i][j].tank = false; board[i][j].plane = false; board[i][j].nuke = false; board[i][j].ind = false; board[i][j].farm = false; board[i][j].men = 0; } } while(compres.farms < superdom_settings.compstartfarms) { i = rb->rand()%BOARD_SIZE + 1; j = rb->rand()%BOARD_SIZE + 1; if((board[i][j].colour == COLOUR_DARK) && (board[i][j].farm == false)) { board[i][j].farm = true; compres.farms++; } } while(compres.inds < superdom_settings.compstartinds) { i = rb->rand()%BOARD_SIZE + 1; j = rb->rand()%BOARD_SIZE + 1; if((board[i][j].colour == COLOUR_DARK) && (board[i][j].ind == false)) { board[i][j].ind = true; compres.inds++; } } while(humanres.farms < superdom_settings.humanstartfarms) { i = rb->rand()%BOARD_SIZE + 1; j = rb->rand()%BOARD_SIZE + 1; if((board[i][j].colour == COLOUR_LIGHT)&&(board[i][j].farm == false)) { board[i][j].farm = true; humanres.farms++; } } while(humanres.inds < superdom_settings.humanstartinds) { i = rb->rand()%BOARD_SIZE + 1; j = rb->rand()%BOARD_SIZE + 1; if((board[i][j].colour == COLOUR_LIGHT) && (board[i][j].ind == false)) { board[i][j].ind = true; humanres.inds++; } } } void draw_board(void) { int i,j; rb->lcd_clear_display(); for(i=1;i<=BOARD_SIZE;i++) { for(j=1;j<=BOARD_SIZE;j++) { if(board[i][j].colour == COLOUR_DARK) { rb->lcd_set_foreground(LCD_DARKGRAY); } else { rb->lcd_set_foreground(LCD_LIGHTGRAY); } rb->lcd_fillrect(MARGIN+(BOX_WIDTH*(i-1)), MARGIN+(BOX_HEIGHT*(j-1)), BOX_WIDTH, BOX_HEIGHT); #if LCD_DEPTH != 16 rb->lcd_set_drawmode(DRMODE_BG | DRMODE_INVERSEVID); #endif if(board[i][j].ind) { MY_BITMAP_PART(superdom_boarditems, board[i][j].colour?ICON_WIDTH:0, 0, ICON_STRIDE, #if LCD_WIDTH > LCD_HEIGHT MARGIN+(BOX_WIDTH*(i-1))+1, MARGIN+(BOX_HEIGHT*(j-1))+ICON_HEIGHT+1, #else MARGIN+(BOX_WIDTH*(i-1))+1+ICON_WIDTH, MARGIN+(BOX_HEIGHT*(j-1))+1, #endif ICON_WIDTH, ICON_HEIGHT); } if(board[i][j].farm) { MY_BITMAP_PART(superdom_boarditems, board[i][j].colour?ICON_WIDTH:0, ICON_HEIGHT, ICON_STRIDE, MARGIN+(BOX_WIDTH*(i-1))+1, MARGIN+(BOX_HEIGHT*(j-1))+1, ICON_WIDTH, ICON_HEIGHT); } if(board[i][j].tank) { MY_BITMAP_PART(superdom_boarditems, board[i][j].colour?ICON_WIDTH:0, ICON_HEIGHT*2, ICON_STRIDE, MARGIN+(BOX_WIDTH*(i-1))+ICON_WIDTH+1, MARGIN+(BOX_HEIGHT*(j-1))+ICON_HEIGHT+1, ICON_WIDTH, ICON_HEIGHT); } if(board[i][j].men) { MY_BITMAP_PART(superdom_boarditems, board[i][j].colour?ICON_WIDTH:0, ICON_HEIGHT*3, #if LCD_WIDTH > LCD_HEIGHT ICON_STRIDE, MARGIN+(BOX_WIDTH*(i-1))+ICON_WIDTH+1, MARGIN+(BOX_HEIGHT*(j-1))+1, #else ICON_STRIDE, MARGIN+(BOX_WIDTH*(i-1))+1, MARGIN+(BOX_HEIGHT*(j-1))+1+ICON_HEIGHT, #endif ICON_WIDTH, ICON_HEIGHT); } if(board[i][j].plane) { MY_BITMAP_PART(superdom_boarditems, board[i][j].colour?ICON_WIDTH:0, ICON_HEIGHT*4, #if LCD_WIDTH > LCD_HEIGHT ICON_STRIDE,MARGIN+(BOX_WIDTH*(i-1))+ICON_WIDTH*2+1, MARGIN+(BOX_HEIGHT*(j-1))+ICON_HEIGHT+1, #else ICON_STRIDE,MARGIN+(BOX_WIDTH*(i-1))+ICON_WIDTH+1, MARGIN+(BOX_HEIGHT*(j-1))+ICON_HEIGHT*2+1, #endif ICON_WIDTH, ICON_HEIGHT); } if(board[i][j].nuke) { MY_BITMAP_PART(superdom_boarditems, board[i][j].colour?ICON_WIDTH:0, ICON_HEIGHT*5, #if LCD_WIDTH > LCD_HEIGHT ICON_STRIDE,MARGIN+(BOX_WIDTH*(i-1))+ICON_WIDTH*2+1, MARGIN+(BOX_HEIGHT*(j-1))+1, #else ICON_STRIDE,MARGIN+(BOX_WIDTH*(i-1))+1, MARGIN+(BOX_HEIGHT*(j-1))+ICON_HEIGHT*2+1, #endif ICON_WIDTH, ICON_HEIGHT); } #if LCD_DEPTH != 16 rb->lcd_set_drawmode(DRMODE_SOLID); #endif } } rb->lcd_set_foreground(LCD_BLACK); /* Draw Horizontal lines */ for(i=0;i<=BOARD_SIZE;i++) { rb->lcd_hline(MARGIN, MARGIN+(BOX_WIDTH*BOARD_SIZE), MARGIN+(BOX_HEIGHT*i)); } /* Draw Vertical lines */ for(i=0;i<=BOARD_SIZE;i++) { rb->lcd_vline(MARGIN+(BOX_WIDTH*i), MARGIN, MARGIN+(BOX_HEIGHT*BOARD_SIZE)); } rb->lcd_update(); } static int calc_strength(int colour, int x, int y) { int a, b, score=0; for (a = -1; a < 2; a++) { for (b = -1; b < 2; b++) { if(board[x+a][y+b].colour==colour) { if(a && b) /* diagonally adjacent, give less influence */ { score += 5; if(board[x + a][y + b].tank || board[x + a][y + b].farm) score += 15; if(board[x + a][y + b].plane || board[x + a][y + b].ind) score += 20; if(board[x + a][y + b].nuke) score += 10; if(board[x + a][y + b].men) score += (board[x + a][y + b].men*133/1000); } else { score += 10; if(board[x + a][y + b].tank || board[x + a][y + b].farm) score += 30; if(board[x + a][y + b].plane || board[x + a][y + b].ind) score += 40; if(board[x + a][y + b].nuke) score += 20; if(board[x + a][y + b].men) score += (board[x + a][y + b].men*133/1000); } } } } return score; } void gen_interest(void) { /* Interest should be around 10% */ int interest = 7+rb->rand()%6; humanres.bank = humanres.bank+(interest*humanres.bank/100); /* Different interest for AI player */ interest = 7+rb->rand()%6; compres.bank = compres.bank+(interest*compres.bank/100); } void draw_cursor(void) { rb->lcd_set_drawmode(DRMODE_COMPLEMENT); rb->lcd_fillrect(MARGIN+((cursor.x-1)*BOX_WIDTH), MARGIN+((cursor.y-1)*BOX_HEIGHT), BOX_WIDTH+1, BOX_HEIGHT+1); rb->lcd_set_drawmode(DRMODE_SOLID); rb->lcd_update(); } void gen_resources(void) { int inccash = 0; int incfood = 0; int ratecash = 0; int ratefood = 0; int i; gen_interest(); /* Generate Human's resources */ for(i=0;irand()%200); } for(i=0;irand()%200); } if(humanres.inds) ratecash = inccash/humanres.inds; if(humanres.farms) ratefood = incfood/humanres.farms; if(ratecash > 450) { if(ratefood > 350) { rb->splash(HZ*2, "Patriotism sweeps the land, all production" " is up this year!"); } else { rb->splash(HZ*2, "Factories working at maximum efficiency," " cash production up this year!"); } } else if(ratecash > 350) { if(ratefood > 350) { rb->splash(HZ*2, "Record crop harvest this year!"); } else if(ratefood > 250) { rb->splash(HZ*2, "Production continues as normal"); } else { rb->splash(HZ*2, "Spoilage of crops leads to reduced farm" " output this year"); } } else { if(ratefood > 350) { rb->splash(HZ*2, "Record crop harvest this year!"); } else if(ratefood > 250) { rb->splash(HZ*2, "Factory unions introduced. Industrial" " production is down this year."); } else { rb->splash(HZ*2, "Internet created. All production is down" " due to time wasted."); } } humanres.cash += inccash; humanres.food += incfood; /* Generate Computer's resources */ inccash = 0; incfood = 0; for(i=0;irand()%200); } for(i=0;irand()%200); } compres.cash += inccash; compres.food += incfood; } static void update_score(void) { int strength; rb->lcd_setfont(FONT_SYSFIXED); rb->lcd_set_drawmode(DRMODE_BG|DRMODE_INVERSEVID); rb->lcd_fillrect(5,LCD_HEIGHT-20,105,20); rb->lcd_set_drawmode(DRMODE_SOLID); strength = calc_strength(COLOUR_LIGHT, cursor.x, cursor.y); rb->lcd_putsxyf(5,LCD_HEIGHT-20,"Your power: %d.%d",strength/10,strength%10); strength = calc_strength(COLOUR_DARK, cursor.x, cursor.y); rb->lcd_putsxyf(5,LCD_HEIGHT-10,"Comp power: %d.%d",strength/10,strength%10); rb->lcd_setfont(FONT_UI); } static int settings_menu(void) { int selection = 0; MENUITEM_STRINGLIST(menu, "Super Domination Settings", NULL, "Computer starting farms", "Computer starting factories", "Human starting farms", "Human starting factories", "Starting cash", "Starting food", "Computer difficulty","Food spoilage", "Persistent units", "Moves per turn"); while(1) { switch(rb->do_menu(&menu, &selection, NULL, false)) { case 0: rb->set_int("Computer starting farms", "", UNIT_INT, &superdom_settings.compstartfarms, NULL, 1, 0, 5, NULL); break; case 1: rb->set_int("Computer starting factories", "", UNIT_INT, &superdom_settings.compstartinds, NULL, 1, 0, 5, NULL); break; case 2: rb->set_int("Human starting farms", "", UNIT_INT, &superdom_settings.humanstartfarms, NULL, 1, 0, 5, NULL); break; case 3: rb->set_int("Human starting factories", "", UNIT_INT, &superdom_settings.humanstartinds, NULL, 1, 0, 5, NULL); break; case 4: rb->set_int("Starting cash", "", UNIT_INT, &superdom_settings.startcash, NULL, 250, 0, 5000, NULL); break; case 5: rb->set_int("Starting food", "", UNIT_INT, &superdom_settings.startfood, NULL, 250, 0, 5000, NULL); break; case 6: { static const struct opt_items difficulty_options[3]={ {"Easy", -1}, {"Intermediate", -1}, {"Hard", -1} }; static int sel=1; rb->set_option("Computer difficulty", &sel, INT, difficulty_options, 3, NULL); superdom_settings.compdiff=sel+1; break; } case 7: rb->set_bool_options("Food spoilage", &superdom_settings.spoil_enabled, "Enabled", 0, "Disabled", 0, NULL); break; case 8: rb->set_bool_options("Persistent units", &superdom_settings.persistent_units, "Enabled", 0, "Disabled", 0, NULL); break; case 9: rb->set_int("Moves per turn", "", UNIT_INT, &superdom_settings.movesperturn, NULL, 1, 1, 5, NULL); break; case MENU_ATTACHED_USB: return RET_VAL_USB; break; case GO_TO_PREVIOUS: return RET_VAL_OK; break; } } return RET_VAL_OK; } static int superdom_help(void) { static char* help_text[] = {"Super Domination","", "Aim", "", "Super", "Domination", "is", "a", "turn-based", "strategy", "game", "where", "the", "aim", "is", "to", "overpower", "the", "computer.", "", "", "How", "to", "Play", "", "Each", "year", "you", "are", "allocated", "an", "amount", "of", "cash", "and", "food,", "depending", "on", "how", "many", "farms", "and", "factories", "you", "control.", "", "Use", "this", "cash", "and", "food", "to", "build", "and", "feed", "your", "army.", "Each", "tile", "has", "a", "strength,", "calculated", "by", "the", "ownership", "of", "surrounding", "tiles,", "and", "the", "type", "and", "number", "of", "troops", "on", "them."}; static struct style_text style[]={ {0, TEXT_CENTER|TEXT_UNDERLINE}, {2, C_RED}, {21, C_RED}, {22, C_RED}, {23, C_RED}, LAST_STYLE_ITEM }; #ifdef HAVE_LCD_COLOR rb->lcd_set_foreground(LCD_WHITE); rb->lcd_set_background(LCD_BLACK); #endif int ret=display_text(ARRAYLEN(help_text), help_text, style, NULL, true); #ifdef HAVE_LCD_COLOR rb->lcd_set_foreground(LCD_BLACK); rb->lcd_set_background(LCD_WHITE); #endif if(ret) return RET_VAL_USB; return RET_VAL_OK; } static int start_menu(void) { int selection = 0; MENUITEM_STRINGLIST(menu, "Super Domination Menu", NULL, "Play Super Domination", "Settings", "Help", "Playback Control", "Quit"); while(1) { switch(rb->do_menu(&menu, &selection, NULL, false)) { case 0: return RET_VAL_OK; /* start playing */ break; case 1: if(settings_menu()==RET_VAL_USB) return RET_VAL_USB; break; case 2: if(superdom_help()==RET_VAL_USB) return RET_VAL_USB; break; case 3: if(playback_control(NULL)) return RET_VAL_USB; break; case 4: return RET_VAL_QUIT_ERR; break; } } return RET_VAL_QUIT_ERR; } static int save_game(void) { int fd; char savepath[MAX_PATH]; rb->snprintf(savepath, sizeof(savepath), "/Savegame.ssg"); if(rb->kbd_input(savepath, MAX_PATH, NULL)) { DEBUGF("Keyboard input failed\n"); return -1; } fd = rb->open(savepath, O_WRONLY|O_CREAT, 0666); DEBUGF("savepath: %s\n", savepath); if(fd < 0) { DEBUGF("Couldn't create/open file\n"); return -1; } rb->write(fd, "SSGv3", 5); rb->write(fd, &gamestate, sizeof(gamestate)); rb->write(fd, &humanres.cash, sizeof(humanres.cash)); rb->write(fd, &humanres.food, sizeof(humanres.food)); rb->write(fd, &humanres.bank, sizeof(humanres.bank)); rb->write(fd, &humanres.planes, sizeof(humanres.planes)); rb->write(fd, &humanres.tanks, sizeof(humanres.tanks)); rb->write(fd, &humanres.men, sizeof(humanres.men)); rb->write(fd, &humanres.nukes, sizeof(humanres.nukes)); rb->write(fd, &humanres.inds, sizeof(humanres.inds)); rb->write(fd, &humanres.farms, sizeof(humanres.farms)); rb->write(fd, &humanres.moves, sizeof(humanres.moves)); rb->write(fd, &compres.cash, sizeof(compres.cash)); rb->write(fd, &compres.food, sizeof(compres.food)); rb->write(fd, &compres.bank, sizeof(compres.bank)); rb->write(fd, &compres.planes, sizeof(compres.planes)); rb->write(fd, &compres.tanks, sizeof(compres.tanks)); rb->write(fd, &compres.men, sizeof(compres.men)); rb->write(fd, &compres.nukes, sizeof(compres.nukes)); rb->write(fd, &compres.inds, sizeof(compres.inds)); rb->write(fd, &compres.farms, sizeof(compres.farms)); rb->write(fd, &compres.moves, sizeof(compres.moves)); rb->write(fd, board, sizeof(board)); rb->write(fd, &superdom_settings.compstartfarms, sizeof(int)); rb->write(fd, &superdom_settings.compstartinds, sizeof(int)); rb->write(fd, &superdom_settings.humanstartfarms, sizeof(int)); rb->write(fd, &superdom_settings.humanstartinds, sizeof(int)); rb->write(fd, &superdom_settings.startcash, sizeof(int)); rb->write(fd, &superdom_settings.startfood, sizeof(int)); rb->write(fd, &superdom_settings.movesperturn, sizeof(int)); rb->close(fd); return 0; } static int ingame_menu(void) { MENUITEM_STRINGLIST(menu, "Super Domination Menu", NULL, "Return to game", "Save Game", "Playback Control", "Quit"); switch(rb->do_menu(&menu, NULL, NULL, false)) { case 0: return RET_VAL_OK; break; case 1: if(!save_game()) rb->splash(HZ, "Game saved"); else rb->splash(HZ, "Error in save"); break; case 2: if(playback_control(NULL)) return RET_VAL_USB; break; case 3: return RET_VAL_QUIT_ERR; break; case MENU_ATTACHED_USB: return RET_VAL_USB; break; case GO_TO_PREVIOUS: return RET_VAL_OK; break; } return RET_VAL_OK; } static int get_number(char* param, int* value, int max) { static const char *button_labels[4][3] = { { "1", "2", "3" }, { "4", "5", "6" }, { "7", "8", "9" }, { "DEL", "0", "OK" } }; int i,j,x=0,y=0; int height, width; int button = 0, ret = RET_VAL_OK; bool done = false; rb->lcd_clear_display(); rb->lcd_getstringsize("DEL", &width, &height); if(width > NUM_BOX_WIDTH || height > NUM_BOX_HEIGHT) rb->lcd_setfont(FONT_SYSFIXED); /* Draw a 3x4 grid */ for(i=0;i<=3;i++) { /* Vertical lines */ rb->lcd_vline(NUM_MARGIN_X+(NUM_BOX_WIDTH*i), NUM_MARGIN_Y, NUM_MARGIN_Y+(4*NUM_BOX_HEIGHT)); } /* Horizontal lines */ for(i=0;i<=4;i++) { rb->lcd_hline(NUM_MARGIN_X, NUM_MARGIN_X+(3*NUM_BOX_WIDTH), NUM_MARGIN_Y+(NUM_BOX_HEIGHT*i)); } for(i=0;i<4;i++) { for(j=0;j<3;j++) { rb->lcd_getstringsize(button_labels[i][j], &width, &height); rb->lcd_putsxy(NUM_MARGIN_X+(j*NUM_BOX_WIDTH)+NUM_BOX_WIDTH/2-width/2, NUM_MARGIN_Y+(i*NUM_BOX_HEIGHT)+NUM_BOX_HEIGHT/2-height/2, button_labels[i][j]); } } rb->lcd_putsxyf(NUM_MARGIN_X+10, NUM_MARGIN_Y+4*NUM_BOX_HEIGHT+10,"%d",*value); rb->lcd_getstringsize(param, &width, &height); if(width < LCD_WIDTH) rb->lcd_putsxy((LCD_WIDTH-width)/2, (NUM_MARGIN_Y-height)/2, param); else rb->lcd_puts_scroll(0, (NUM_MARGIN_Y/height-1)/2, param); rb->lcd_set_drawmode(DRMODE_COMPLEMENT); rb->lcd_fillrect(NUM_MARGIN_X+(NUM_BOX_WIDTH*x), NUM_MARGIN_Y+(NUM_BOX_HEIGHT*y), NUM_BOX_WIDTH+1, NUM_BOX_HEIGHT+1); rb->lcd_set_drawmode(DRMODE_SOLID); rb->lcd_update(); while(!done) { button=pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts)); rb->lcd_set_drawmode(DRMODE_COMPLEMENT); rb->lcd_fillrect(NUM_MARGIN_X+(NUM_BOX_WIDTH*x), NUM_MARGIN_Y+(NUM_BOX_HEIGHT*y), NUM_BOX_WIDTH+1, NUM_BOX_HEIGHT+1); rb->lcd_set_drawmode(DRMODE_SOLID); switch(button) { case SUPERDOM_OK: if(y!=3) { *value *= 10; *value += button_labels[y][x][0] - '0'; } else if(x==0) { *value /= 10; } else if(x==1) { *value *= 10; } else if(x==2) { done = true; break; } if ((unsigned) *value > (unsigned) max) *value = max; rb->lcd_set_drawmode(DRMODE_BG|DRMODE_INVERSEVID); rb->lcd_fillrect(0, NUM_MARGIN_Y+4*NUM_BOX_HEIGHT+10, LCD_WIDTH, 30); rb->lcd_set_drawmode(DRMODE_SOLID); rb->lcd_putsxyf(NUM_MARGIN_X+10,NUM_MARGIN_Y+4*NUM_BOX_HEIGHT+10, "%d", *value); break; case SUPERDOM_CANCEL: *value = 0; done = true; ret = RET_VAL_QUIT_ERR; break; #if CONFIG_KEYPAD != IRIVER_H10_PAD case SUPERDOM_LEFT: if(x==0) { x=2; } else { x--; } break; case SUPERDOM_RIGHT: if(x==2) { x=0; } else { x++; } break; #endif case SUPERDOM_UP: if(y==0) { #if CONFIG_KEYPAD == IRIVER_H10_PAD if(x > 0) x--; else x=2; #endif y=3; } else { y--; } break; case SUPERDOM_DOWN: if(y==3) { #if CONFIG_KEYPAD == IRIVER_H10_PAD if(x < 2) x++; else x=0; #endif /* CONFIG_KEYPAD == IRIVER_H10_PAD */ y=0; } else { y++; } break; default: if(rb->default_event_handler(button) == SYS_USB_CONNECTED) { done = true; ret = RET_VAL_USB; } break; } rb->lcd_set_drawmode(DRMODE_COMPLEMENT); rb->lcd_fillrect(NUM_MARGIN_X+(NUM_BOX_WIDTH*x), NUM_MARGIN_Y+(NUM_BOX_HEIGHT*y), NUM_BOX_WIDTH+1, NUM_BOX_HEIGHT+1); rb->lcd_set_drawmode(DRMODE_SOLID); rb->lcd_update(); } rb->lcd_setfont(FONT_UI); rb->lcd_scroll_stop(); if (ret == RET_VAL_QUIT_ERR) rb->splash(HZ, "Cancelled"); return ret; } static bool tile_has_item(int type, int x, int y) { switch(type) { case 0: return (board[x][y].men > 0); break; case 1: return board[x][y].tank; break; case 2: return board[x][y].plane; break; case 3: return board[x][y].farm; break; case 4: return board[x][y].ind; break; case 5: return board[x][y].nuke; break; } return false; } static int buy_resources(int colour, int type, int x, int y, int nummen) { const char *itemnames[][6] = { { "them", "the tank", "the plane", "the farm", "the industrial plant", "the nuke", }, { "place men", "place a tank", "place a plane", "build a farm", "build an industrial plant", "place a nuke", }, { NULL, "a tank", "a plane", "a farm", "an industrial plant", "a nuke", }, }; bool human = (colour == COLOUR_LIGHT); int price = 0; int temp; struct resources *res; if(human) { res = &humanres; } else { res = &compres; } switch(type) { case 0: /* men */ price = PRICE_MEN*nummen; break; case 1: /* tank */ price = PRICE_TANK; break; case 2: /* plane */ price = PRICE_PLANE; break; case 3: /* Farm */ price = PRICE_FARM; break; case 4: /* Factory */ price = PRICE_FACTORY; break; case 5: /* nuke */ price = PRICE_NUKE; break; } if(res->cash < price) { if(human) rb->splash(HZ, "Not enough money!"); return RET_VAL_QUIT_ERR; } if(human) { rb->splashf(HZ, "Where do you want to place %s?", itemnames[0][type]); if((temp = select_square()) != RET_VAL_OK) return temp; x = cursor.x; y = cursor.y; } if(board[x][y].colour != colour) { if(human) rb->splashf(HZ, "Can't %s on enemy territory", itemnames[1][type]); return RET_VAL_QUIT_ERR; } if(type != 0 && tile_has_item(type, x, y)) { if(human) rb->splashf(HZ, "There is already %s there", itemnames[2][type]); return RET_VAL_QUIT_ERR; } switch(type) { case 0: board[x][y].men += nummen; res->men += nummen; break; case 1: board[x][y].tank = true; res->tanks++; break; case 2: board[x][y].plane = true; res->planes++; break; case 3: board[x][y].farm = true; res->farms++; break; case 4: board[x][y].ind = true; res->inds++; break; case 5: board[x][y].nuke = true; res->nukes++; break; } res->cash -= price; draw_board(); rb->sleep(HZ); return RET_VAL_OK; } static int buy_resources_menu(void) { int selection = 0,nummen; MENUITEM_STRINGLIST(menu, "Buy Resources", NULL, "Buy men ($" PRICE_MEN_STR ")", "Buy tank ($" PRICE_TANK_STR ")", "Buy plane ($" PRICE_PLANE_STR ")", "Buy Farm ($" PRICE_FARM_STR ")", "Buy Factory ($" PRICE_FACTORY_STR ")", "Buy Nuke ($" PRICE_NUKE_STR ")", "Finish buying"); while(1) { switch(rb->do_menu(&menu, &selection, NULL, false)) { case 0: nummen = 0; if(get_number("How many men would you like?", &nummen, humanres.cash) == RET_VAL_USB) return RET_VAL_USB; if(!nummen) break; /* fall through */ case 1: case 2: case 3: case 4: case 5: if(buy_resources(COLOUR_LIGHT, selection, 0, 0, nummen)== RET_VAL_USB) return RET_VAL_USB; break; case 6: return RET_VAL_OK; break; case MENU_ATTACHED_USB: return RET_VAL_USB; break; case GO_TO_PREVIOUS: return RET_VAL_OK; break; } } return RET_VAL_OK; } static int move_unit(int colour, int type, int fromx, int fromy, int tox, int toy, int nummen) { const char *itemnames[][3] = { { "troops", "the tank", "the plane", }, { "any troops", "a tank", "a plane", }, { "the troops", "the tank", "the plane", } }; bool human = (colour == COLOUR_LIGHT); int temp; if(human) { rb->splashf(HZ, "Select where you want to move %s from", itemnames[0][type]); if((temp = select_square()) != RET_VAL_OK) return temp; fromx = cursor.x; fromy = cursor.y; } if(board[fromx][fromy].colour != colour) { if(human) rb->splash(HZ, "That isn't your territory"); return RET_VAL_QUIT_ERR; } if(!tile_has_item(type, fromx, fromy)) { if(human) rb->splashf(HZ, "You don't have %s there", itemnames[1][type]); return RET_VAL_QUIT_ERR; } if(type == 0) { if(human) { nummen = board[fromx][fromy].men; if((temp = get_number("How many men do you want to move?", &nummen, nummen)) != RET_VAL_OK) return temp; } if(nummen > board[fromx][fromy].men) { if(human) rb->splash(HZ, "You don't have that many troops."); return RET_VAL_QUIT_ERR; } } if(human) { rb->splashf(HZ, "Select where you want to move %s to", itemnames[2][type]); if((temp = select_square()) != RET_VAL_OK) return temp; tox = cursor.x; toy = cursor.y; } if((tox == fromx && toy == fromy) || board[tox][toy].colour != colour || (type != 2 && (abs(tox - fromx) > 1 || abs(toy - fromy) > 1))) { if(human) rb->splash(HZ, "Invalid move"); return RET_VAL_QUIT_ERR; } if(type != 0 && tile_has_item(type, tox, toy)) { if(human) rb->splashf(HZ, "There is already %s there", itemnames[1][type]); return RET_VAL_QUIT_ERR; } switch(type) { case 0: board[fromx][fromy].men -= nummen; board[tox][toy].men += nummen; break; case 1: board[fromx][fromy].tank = false; board[tox][toy].tank = true; break; case 2: board[fromx][fromy].plane = false; board[tox][toy].plane = true; break; } return RET_VAL_OK; } static int move_unit_menu(void) { int selection = 0; MENUITEM_STRINGLIST(menu, "Move unit", NULL, "Move men", "Move tank", "Move plane"); switch(rb->do_menu(&menu, &selection, NULL, false)) { case 0: case 1: case 2: switch(move_unit(COLOUR_LIGHT, selection, 0, 0, 0, 0, 0)) { case RET_VAL_OK: humanres.moves--; break; case RET_VAL_USB: return RET_VAL_USB; break; } break; case MENU_ATTACHED_USB: return RET_VAL_USB; } return RET_VAL_OK; } static int launch_nuke(int colour, int nukex, int nukey, int targetx, int targety) { bool human = (colour == COLOUR_LIGHT); int temp; struct resources *res; if(board[nukex][nukey].colour != colour) { if(human) rb->splash(HZ, "That isn't your territory"); return RET_VAL_QUIT_ERR; } if(! board[nukex][nukey].nuke) { if(human) rb->splashf(HZ, "You don't have a nuke there"); return RET_VAL_QUIT_ERR; } if(human) { rb->splash(HZ, "Select place to target with nuke"); if((temp = select_square()) != RET_VAL_OK) return temp; targetx = cursor.x; targety = cursor.y; } if(human) { humanres.nukes--; } else { compres.nukes--; } board[nukex][nukey].nuke = false; if(board[targetx][targety].colour == COLOUR_LIGHT) { res = &humanres; } else { res = &compres; } res->men -= board[targetx][targety].men; res->tanks -= board[targetx][targety].tank; res->planes -= board[targetx][targety].plane; res->nukes -= board[targetx][targety].nuke; res->farms -= board[targetx][targety].farm; res->inds -= board[targetx][targety].ind; board[targetx][targety].men = 0; board[targetx][targety].tank = false; board[targetx][targety].plane = false; board[targetx][targety].ind = false; board[targetx][targety].nuke = false; board[targetx][targety].farm = false; /* TODO: Fallout carried by wind */ draw_board(); if(human) rb->sleep(HZ*2); else rb->sleep(HZ); return RET_VAL_OK; } static int movement_menu(void) { int selection = 0, temp; MENUITEM_STRINGLIST(menu, "Movement", NULL, "Move unit", "Buy additional moves ($" PRICE_MOVE_STR ")", "Launch nuclear missile", "Check map", "Finish moving", "Game menu"); while(1) { switch(rb->do_menu(&menu, &selection, NULL, false)) { case 0: if(humanres.moves) { if(move_unit_menu()==RET_VAL_USB) return RET_VAL_USB; } else { rb->splash(HZ, "You have no more moves left." " You can buy more for $" PRICE_MOVE_STR " each."); } break; case 1: if(humanres.cash > PRICE_MOVE) { humanres.moves++; humanres.cash -= PRICE_MOVE; rb->snprintf(buf, sizeof(buf), "You now have %d moves", humanres.moves); rb->splash(HZ, buf); } else { rb->splash(HZ, "Not enough money!"); } break; case 2: if(humanres.nukes==0) { rb->splash(HZ, "You do not have any nukes to launch"); } else { rb->splash(HZ, "Select place to launch nuke from"); switch(select_square()) { case RET_VAL_OK: if(launch_nuke(COLOUR_LIGHT, cursor.x, cursor.y, 0, 0) == RET_VAL_USB) return RET_VAL_USB; break; case RET_VAL_USB: return RET_VAL_USB; break; } } break; case 3: if(select_square() == RET_VAL_USB) return RET_VAL_USB; break; case 4: return RET_VAL_OK; break; case 5: if((temp = ingame_menu()) != RET_VAL_OK) return temp; break; case MENU_ATTACHED_USB: return RET_VAL_USB; break; } } return RET_VAL_OK; } static const char* inventory_data(int selected_item, void * data, char * buffer, size_t buffer_len) { (void)data; switch(selected_item) { case 0: rb->snprintf(buffer,buffer_len,"Men: %d", humanres.men); break; case 1: rb->snprintf(buffer,buffer_len,"Tanks: %d", humanres.tanks); break; case 2: rb->snprintf(buffer,buffer_len,"Planes: %d", humanres.planes); break; case 3: rb->snprintf(buffer,buffer_len,"Factories: %d", humanres.inds); break; case 4: rb->snprintf(buffer,buffer_len,"Farms: %d", humanres.farms); break; case 5: rb->snprintf(buffer,buffer_len,"Nukes: %d", humanres.nukes); break; case 6: rb->snprintf(buffer,buffer_len,"Cash: %d", humanres.cash); break; case 7: rb->snprintf(buffer,buffer_len,"Food: %d", humanres.food); break; case 8: rb->snprintf(buffer,buffer_len,"Bank: %d", humanres.bank); break; default: return NULL; } return buffer; } static int show_inventory(void) { struct simplelist_info info; rb->simplelist_info_init(&info, "Inventory", 9, NULL); info.get_name = inventory_data; if(rb->simplelist_show_list(&info)) { return RET_VAL_USB; } else { return RET_VAL_OK; } } static int production_menu(void) { int selection = 0, temp; MENUITEM_STRINGLIST(menu, "Production", NULL, "Buy resources", "Show inventory", "Check map", "Invest money", "Withdraw money", "Finish turn", "Game menu"); while(1) { switch(rb->do_menu(&menu, &selection, NULL, false)) { case 0: if(buy_resources_menu() == RET_VAL_USB) return RET_VAL_USB; break; case 1: if(show_inventory() == RET_VAL_USB) return RET_VAL_USB; break; case 2: if(select_square() == RET_VAL_USB) return RET_VAL_USB; break; case 3: temp = humanres.cash; if(get_number("How much do you want to invest?", &temp, humanres.cash) == RET_VAL_USB) return RET_VAL_USB; if(temp > humanres.cash) { rb->splash(HZ, "You don't have that much cash to invest"); } else { humanres.cash -= temp; humanres.bank += temp; } break; case 4: temp = humanres.bank; if(get_number("How much do you want to withdraw?", &temp, humanres.bank) == RET_VAL_USB) return RET_VAL_USB; if(temp > humanres.bank) { rb->splash(HZ, "You don't have that much cash to withdraw"); } else { humanres.cash += temp; humanres.bank -= temp; } break; case 5: return RET_VAL_OK; break; case 6: if((temp = ingame_menu()) != RET_VAL_OK) return temp; break; case MENU_ATTACHED_USB: return RET_VAL_USB; break; } } return RET_VAL_OK; } static void init_resources(void) { humanres.cash = superdom_settings.startcash; humanres.food = superdom_settings.startfood; humanres.tanks = 0; humanres.planes = 0; humanres.nukes = 0; humanres.inds = 0; humanres.farms = 0; humanres.men = 0; humanres.bank = 0; humanres.moves = 0; compres.cash = superdom_settings.startcash; compres.food = superdom_settings.startfood; compres.tanks = 0; compres.planes = 0; compres.nukes = 0; compres.inds = 0; compres.farms = 0; compres.men = 0; compres.bank = 0; compres.moves = 0; } static int select_square(void) { int button = 0; draw_board(); draw_cursor(); update_score(); #if LCD_WIDTH >= 220 rb->lcd_setfont(FONT_SYSFIXED); rb->lcd_putsxyf(125, LCD_HEIGHT-20,"Cash: %d", humanres.cash); rb->lcd_putsxyf(125, LCD_HEIGHT-10,"Food: %d", humanres.food); rb->lcd_setfont(FONT_UI); #endif rb->lcd_update(); while(1) { button=pluginlib_getaction(-1, plugin_contexts, ARRAYLEN(plugin_contexts)); switch(button) { case SUPERDOM_CANCEL: rb->splash(HZ, "Cancelled"); return RET_VAL_QUIT_ERR; break; case SUPERDOM_OK: return RET_VAL_OK; break; case SUPERDOM_LEFT: case SUPERDOM_LEFT_REPEAT: draw_cursor(); /* Deselect the current tile */ if(cursor.x>1) { cursor.x--; } else { cursor.x = BOARD_SIZE; } update_score(); draw_cursor(); break; case SUPERDOM_RIGHT: case SUPERDOM_RIGHT_REPEAT: draw_cursor(); /* Deselect the current tile */ if(cursor.x1) { cursor.y--; } else { #if CONFIG_KEYPAD == IRIVER_H10_PAD if(cursor.x > 1) cursor.x--; else cursor.x = BOARD_SIZE; #endif cursor.y = BOARD_SIZE; } update_score(); draw_cursor(); break; case SUPERDOM_DOWN: case SUPERDOM_DOWN_REPEAT: draw_cursor(); /* Deselect the current tile */ if(cursor.ydefault_event_handler(button) == SYS_USB_CONNECTED) { return RET_VAL_USB; } } } } static int killmen(int colour) { bool human = (colour == COLOUR_LIGHT); int menkilled,i,j; int percent; if(human) { percent = (humanres.food*1000)/humanres.men; humanres.food = 0; } else { percent = (compres.food*1000)/compres.men; compres.food = 0; } menkilled = 0; for(i=1;i<=BOARD_SIZE;i++) { for(j=1;j<=BOARD_SIZE;j++) { if(board[i][j].colour == colour) { int nummen = ((board[i][j].men * percent)/1000); menkilled += board[i][j].men - nummen; board[i][j].men = nummen; } } } if(human) humanres.men -= menkilled; else compres.men -= menkilled; return menkilled; } /* return -1 if error, 1 if attack is succeeded, 0 otherwise */ static int attack_territory(int colour, int x, int y) { bool human = (colour == COLOUR_LIGHT); int str_diff; if(board[x][y].colour == colour) { if(human) rb->splash(HZ, "You can't attack your own territory"); return -1; } str_diff = calc_strength(COLOUR_DARK, x, y) - calc_strength(COLOUR_LIGHT, x, y); if(human) { str_diff = -str_diff; } if(str_diff > 0 || (str_diff == 0 && rb->rand()%2)) { struct resources *offres, *defres; if(human) { offres = &humanres; defres = &compres; } else { offres = &compres; defres = &humanres; } defres->men -= board[x][y].men; defres->tanks -= board[x][y].tank; defres->planes -= board[x][y].plane; defres->nukes -= board[x][y].nuke; defres->farms -= board[x][y].farm; defres->inds -= board[x][y].ind; offres->farms += board[x][y].farm; offres->inds += board[x][y].ind; offres->nukes += board[x][y].nuke; board[x][y].colour = colour; if(!superdom_settings.persistent_units) { board[x][y].men = 0; board[x][y].tank = false; board[x][y].plane = false; } draw_board(); if(human) rb->sleep(HZ*2); else rb->sleep(HZ); return 1; } else { if(human) rb->splash(HZ, "Your troops were unable to overcome" " the enemy troops"); else rb->splash(HZ*2, "The computer attempted to " "attack, but the invasion was" " pushed back"); return 0; } return 0; } static void spoil_food(void) { /* spoil 0-10% of food, different amounts for computer/player */ int spoil_amount=humanres.food*(0.1*rb->rand()/RAND_MAX); if(spoil_amount) rb->splashf(2*HZ, "Spoilage claims %d units of food", spoil_amount); humanres.food-=spoil_amount; /* now for computer */ spoil_amount=compres.food*(0.1*rb->rand()/RAND_MAX); compres.food-=spoil_amount; } static int war_menu(void) { int selection = 0, temp; MENUITEM_STRINGLIST(menu, "War!", NULL, "Select territory to attack", "Finish turn", "Game menu"); while(humanres.moves) { switch(rb->do_menu(&menu, &selection, NULL, false)) { case 0: switch(select_square()) { case RET_VAL_OK: if(attack_territory(COLOUR_LIGHT, cursor.x, cursor.y) >= 0) humanres.moves--; break; case RET_VAL_USB: return RET_VAL_USB; break; } break; case 1: return RET_VAL_OK; break; case 2: if((temp = ingame_menu()) != RET_VAL_OK) return temp; break; } } return RET_VAL_OK; } struct threat { int x; int y; int str_diff; }; static bool place_adjacent(bool tank, int x, int y) { int type = (tank ? 1: 2); if(!buy_resources(COLOUR_DARK, type, x, y, 0)) { return true; } if(!buy_resources(COLOUR_DARK, type, x-1, y, 0)) { return true; } if(!buy_resources(COLOUR_DARK, type, x+1, y, 0)) { return true; } if(!buy_resources(COLOUR_DARK, type, x, y-1, 0)) { return true; } if(!buy_resources(COLOUR_DARK, type, x, y+1, 0)) { return true; } return false; } static bool has_adjacent(int x, int y) { if ((board[x][y].colour == COLOUR_LIGHT) && ((board[x-1][y].colour == COLOUR_DARK) || (board[x+1][y].colour == COLOUR_DARK) || (board[x][y+1].colour == COLOUR_DARK) || (board[x][y-1].colour == COLOUR_DARK))) return 1; else return 0; } static void find_adjacent(int x, int y, int* adj_x, int* adj_y) { /* Finds adjacent squares, returning squares without tanks on them * in preference to those with them */ if(board[x-1][y].colour == COLOUR_DARK) { *adj_x = x-1; *adj_y = y; return; } if(board[x+1][y].colour == COLOUR_DARK) { *adj_x = x+1; *adj_y = y; return; } if(board[x][y-1].colour == COLOUR_DARK) { *adj_x = x; *adj_y = y-1; return; } if(board[x][y+1].colour == COLOUR_DARK) { *adj_x = x; *adj_y = y+1; return; } } static void computer_allocate(void) { /* Firstly, decide whether to go offensive or defensive. * This is primarily decided by the human player posing a threat to either * the computer's farms, factories or nukes */ int i, j, k; bool offensive = true; struct threat threats[4]; int numthreats = 0; int total_str_diff = 0; int numterritory = 0; int str_diff; int men_needed; struct threat targets[2]; int numtargets; struct cursor adj = { 0, 0 }; compres.cash += compres.bank; compres.bank = 0; for(i=1;i<=BOARD_SIZE;i++) { for(j=1;j<=BOARD_SIZE;j++) { if(board[i][j].colour == COLOUR_DARK) { numterritory++; str_diff = calc_strength(COLOUR_LIGHT,i,j) - calc_strength(COLOUR_DARK,i,j); if(str_diff > 0 && (board[i][j].ind || board[i][j].farm || board[i][j].nuke)) { /* computer's farm/factory/nuke is being threatened */ if(numthreats < 3) { offensive = false; threats[numthreats].x = i; threats[numthreats].y = j; threats[numthreats].str_diff = str_diff; numthreats++; } } } rb->yield(); } } /* if the computer has no factories, build some ASAP */ if(!compres.inds) { while(compres.cash >= PRICE_FACTORY && compres.inds < numterritory) { i = rb->rand()%BOARD_SIZE + 1; j = rb->rand()%BOARD_SIZE + 1; if(board[i][j].colour == COLOUR_DARK) { buy_resources(COLOUR_DARK, 4, i, j, 0); } } } if(superdom_settings.compdiff>=AI_BUILD_INDS_FARMS_LEVEL && compres.cash>=PRICE_FACTORY) { while(compres.cash>=PRICE_FACTORY) { if(compres.farms=PRICE_FARM) { i = rb->rand()%BOARD_SIZE + 1; j = rb->rand()%BOARD_SIZE + 1; if(board[i][j].colour == COLOUR_DARK && !board[i][j].farm) { buy_resources(COLOUR_DARK, 3, i, j, 0); break; } } } else { while(compres.inds=PRICE_FACTORY) { i = rb->rand()%BOARD_SIZE + 1; j = rb->rand()%BOARD_SIZE + 1; if(board[i][j].colour == COLOUR_DARK && !board[i][j].ind) { buy_resources(COLOUR_DARK, 4, i, j, 0); break; } } } } } /* AI will buy nukes first if possible */ if(compres.cash > PRICE_NUKE + PRICE_TANK && superdom_settings.compdiff>=AI_BUILD_NUKES_LEVEL) { while(compres.cash >= PRICE_NUKE && compres.nukes < numterritory) { i = rb->rand()%BOARD_SIZE + 1; j = rb->rand()%BOARD_SIZE + 1; if(board[i][j].colour == COLOUR_DARK) { buy_resources(COLOUR_DARK, 5, i, j, 0); } rb->yield(); } } if(offensive) { /* The AI is going to go straight for the throat here and attack * the player's farms, nukes, and factories. The amount of cash * the AI has to spend will determine how many targets there are */ if(compres.cash > 1200) { /* 1200 is a figure I pulled out of nowhere. Adjust as needed */ numtargets = 2; } else { numtargets = 1; } /* Work out which target(s) to attack. They must have adjacent squares * owned by the computer. If none are found just place troops in * random places around the map until we run out of money */ k = 0; for(i=1;i<=BOARD_SIZE;i++) { for(j=1;j<=BOARD_SIZE;j++) { if(has_adjacent(i,j) && (board[i][j].ind || board[i][j].farm || board[i][j].nuke)) { if(kyield(); } } if(k == 0) { /* randomly place tanks */ while(compres.cash >= PRICE_TANK && compres.tanks < numterritory) { i = rb->rand()%BOARD_SIZE + 1; j = rb->rand()%BOARD_SIZE + 1; if(board[i][j].colour == COLOUR_DARK) { buy_resources(COLOUR_DARK, 1, i, j, 0); } rb->yield(); } } else { for(i=0;i 0 && compres.cash > 0) { /* While we still need them keep placing men */ if(!place_adjacent(true, targets[i].x, targets[i].y)) { find_adjacent(targets[i].x, targets[i].y, &adj.x, &adj.y); men_needed = (str_diff + 20)*1000/133; if(compres.cash < men_needed) { men_needed = compres.cash; } buy_resources(COLOUR_DARK, 0, adj.x, adj.y, men_needed); break; } str_diff = calc_strength(COLOUR_LIGHT, targets[i].x, targets[i].y) - calc_strength(COLOUR_DARK, targets[i].x, targets[i].y); } } } } else { /* Work out what to place on each square to defend it. * Tanks are preferential because they do not require food, * but if the budget is tight then we fall back onto troops. * Conversely if cash is not an issue and there are already tanks in * place planes will be deployed. We would like a margin of at least * 20 points to be safe. */ for(i=0;i compres.cash) { /* Not enough cash to accomodate all threats using tanks alone - * use men as a backup */ for(i=0;i= compres.cash); for(i=0;i 0) { if(!place_adjacent(tank, threats[i].x, threats[i].y)) { /* No room for any more planes or tanks, revert to * men */ find_adjacent(threats[i].x, threats[i].y, &adj.x, &adj.y); men_needed = (str_diff + 20)*1000/133; if(compres.cash < men_needed) { men_needed = compres.cash; } buy_resources(COLOUR_DARK, 0, threats[i].x, threats[i].y, men_needed); break; } str_diff = calc_strength(COLOUR_LIGHT, threats[i].x, threats[i].y) - calc_strength(COLOUR_DARK, threats[i].x, threats[i].y); } } } } /* no investing in easy mode */ if(superdom_settings.compdiff>=AI_INVESTING_LEVEL) { compres.bank += compres.cash; compres.cash = 0; } } static int find_adj_target(int x, int y, struct cursor* adj) { /* Find a square next to a computer's farm or factory owned by the player * that is vulnerable. Return 1 on success, 0 otherwise */ if(board[x+1][y].colour == COLOUR_LIGHT && calc_strength(COLOUR_LIGHT,x+1,y)<=calc_strength(COLOUR_DARK,x+1,y)) { adj->x = x+1; adj->y = y; return 1; } if(board[x-1][y].colour == COLOUR_LIGHT && calc_strength(COLOUR_LIGHT,x-1,y)<=calc_strength(COLOUR_DARK,x-1,y)) { adj->x = x-1; adj->y = y; return 1; } if(board[x][y+1].colour == COLOUR_LIGHT && calc_strength(COLOUR_LIGHT,x,y+1)<=calc_strength(COLOUR_DARK,x,y+1)) { adj->x = x; adj->y = y+1; return 1; } if(board[x][y-1].colour == COLOUR_LIGHT && calc_strength(COLOUR_LIGHT,x,y-1)<=calc_strength(COLOUR_DARK,x,y-1)) { adj->x = x; adj->y = y-1; return 1; } return 0; } static void computer_movement(void) { /* use nukes */ if(superdom_settings.compdiff>=3) { struct cursor nukes[10]; /* 10 for now, change as needed */ int nukes_back=0; if(compres.nukes>0) { for(int i=1;i<=BOARD_SIZE && nukes_backyield(); } } bool found_target=true; struct cursor adj; int next_nuke=0; /* first, use nukes for defence */ while(found_target && next_nukesplashf(2*HZ, "Launching nuke for defence, from (%d, %d) to (%d, %d)", nukes[next_nuke].x, nukes[next_nuke].y, adj.x, adj.y); launch_nuke(COLOUR_DARK, nukes[next_nuke].x, nukes[next_nuke].y, adj.x, adj.y); next_nuke++; } rb->yield(); } } } /* if we still have any left over, use those for offence */ found_target = true; while(found_target && next_nuke= calc_strength(COLOUR_LIGHT, i, j))) { found_target = true; rb->splashf(2*HZ, "Launching nuke for offence, nuke index %d", next_nuke); launch_nuke(COLOUR_DARK, nukes[next_nuke].x, nukes[next_nuke].y, i, j); next_nuke++; } rb->yield(); } } } } } /* TODO: move other units */ } static void computer_war(void) { /* Work out where to attack - prioritise the defence of buildings and nukes */ int i, j; bool found_target = true; struct cursor adj; while(found_target) { found_target = false; for(i=1;i<=BOARD_SIZE;i++) { for(j=1;j<=BOARD_SIZE;j++) { if((board[i][j].colour == COLOUR_DARK) && (board[i][j].farm || board[i][j].ind || board[i][j].nuke) && find_adj_target(i, j, &adj)) { found_target = true; if(attack_territory(COLOUR_DARK, adj.x, adj.y) >= 0) { compres.moves--; if(!compres.moves) return; } } rb->yield(); } } } /* Defence stage done, move on to OFFENCE */ found_target = true; while(found_target) { found_target = false; for(i=1;i<=BOARD_SIZE;i++) { for(j=1;j<=BOARD_SIZE;j++) { if(board[i][j].colour == COLOUR_LIGHT && (board[i][j].ind || board[i][j].farm || board[i][j].nuke) && (calc_strength(COLOUR_DARK, i, j) >= calc_strength(COLOUR_LIGHT, i, j))) { found_target = true; if(attack_territory(COLOUR_DARK, i, j) >= 0) { compres.moves--; if(!compres.moves) return; } } rb->yield(); } } } /* Spend leftover moves wherever attacking randomly */ found_target = true; while(found_target) { found_target = false; for(i=1;i<=BOARD_SIZE;i++) { for(j=1;j<=BOARD_SIZE;j++) { if(board[i][j].colour == COLOUR_LIGHT && (calc_strength(COLOUR_DARK, i, j) >= calc_strength(COLOUR_LIGHT, i, j))) { found_target = true; if(attack_territory(COLOUR_DARK, i, j) >= 0) { compres.moves--; if(!compres.moves) return; } } rb->yield(); } } } } static int load_game(const char* file) { int fd; fd = rb->open(file, O_RDONLY); if(fd < 0) { DEBUGF("Couldn't open savegame\n"); return -1; } rb->read(fd, buf, 5); if(rb->strcmp(buf, "SSGv3")) { rb->splash(HZ, "Invalid/incompatible savegame"); return -1; } rb->read(fd, &gamestate, sizeof(gamestate)); rb->read(fd, &humanres.cash, sizeof(humanres.cash)); rb->read(fd, &humanres.food, sizeof(humanres.food)); rb->read(fd, &humanres.bank, sizeof(humanres.bank)); rb->read(fd, &humanres.planes, sizeof(humanres.planes)); rb->read(fd, &humanres.tanks, sizeof(humanres.tanks)); rb->read(fd, &humanres.men, sizeof(humanres.men)); rb->read(fd, &humanres.nukes, sizeof(humanres.nukes)); rb->read(fd, &humanres.inds, sizeof(humanres.inds)); rb->read(fd, &humanres.farms, sizeof(humanres.farms)); rb->read(fd, &humanres.moves, sizeof(humanres.moves)); rb->read(fd, &compres.cash, sizeof(humanres.cash)); rb->read(fd, &compres.food, sizeof(humanres.food)); rb->read(fd, &compres.bank, sizeof(humanres.bank)); rb->read(fd, &compres.planes, sizeof(humanres.planes)); rb->read(fd, &compres.tanks, sizeof(humanres.tanks)); rb->read(fd, &compres.men, sizeof(humanres.men)); rb->read(fd, &compres.nukes, sizeof(humanres.nukes)); rb->read(fd, &compres.inds, sizeof(humanres.inds)); rb->read(fd, &compres.farms, sizeof(humanres.farms)); rb->read(fd, &compres.moves, sizeof(humanres.moves)); rb->read(fd, board, sizeof(board)); rb->read(fd, &superdom_settings.compstartfarms, sizeof(int)); rb->read(fd, &superdom_settings.compstartinds, sizeof(int)); rb->read(fd, &superdom_settings.humanstartfarms, sizeof(int)); rb->read(fd, &superdom_settings.humanstartinds, sizeof(int)); rb->read(fd, &superdom_settings.startcash, sizeof(int)); rb->read(fd, &superdom_settings.startfood, sizeof(int)); rb->read(fd, &superdom_settings.movesperturn, sizeof(int)); rb->close(fd); return 0; } static void default_settings(void) { superdom_settings.compstartfarms = 1; superdom_settings.compstartinds = 1; superdom_settings.humanstartfarms = 2; superdom_settings.humanstartinds = 2; superdom_settings.startcash = 0; superdom_settings.startfood = 0; superdom_settings.movesperturn = 2; superdom_settings.compdiff=2; superdom_settings.spoil_enabled=false; superdom_settings.persistent_units=false; } static int average_strength(int colour) { /* This function calculates the average strength of the given player, * used to determine when the computer wins or loses. */ int i,j; int totalpower = 0; for(i=1;i<=BOARD_SIZE;i++) { for(j=1;j<=BOARD_SIZE;j++) { if(board[i][j].colour != -1) { totalpower += calc_strength(colour, i, j); } } } return totalpower/NUM_SPACES; } enum plugin_status plugin_start(const void* parameter) { rb->srand(*rb->current_tick); #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); rb->lcd_set_foreground(LCD_BLACK); rb->lcd_set_background(LCD_WHITE); #endif cursor.x = 1; cursor.y = 1; default_settings(); if(parameter) { if(load_game(parameter) != 0) { DEBUGF("Loading failed, generating new game\n"); } else { switch(gamestate) { case GS_PROD: goto startprod; break; case GS_MOVE: goto startmove; break; case GS_WAR: goto startwar; break; default: goto startyear; break; } } } switch(start_menu()) { case RET_VAL_OK: /* start playing */ break; case RET_VAL_QUIT_ERR: /* quit */ return PLUGIN_OK; break; case RET_VAL_USB: return PLUGIN_USB_CONNECTED; break; } init_resources(); init_board(); gen_resources(); startyear: while(1) { int avg_str_diff = (average_strength(COLOUR_LIGHT) - average_strength(COLOUR_DARK)); /* computer will hold out longer in hard mode */ if(avg_str_diff > (superdom_settings.compdiff>=3 ? COMPUTER_HARD_SURRENDER_THRESHOLD : COMPUTER_SURRENDER_THRESHOLD)) { rb->splash(HZ*4, "The computer has surrendered. You win."); return PLUGIN_OK; } if(-avg_str_diff > HUMAN_SURRENDER_THRESHOLD) { rb->splash(HZ*4, "Your army has suffered terrible morale from" " the bleak prospects of winning. You lose."); return PLUGIN_OK; } /* production */ startprod: gamestate = GS_PROD; switch(production_menu()) { case RET_VAL_USB: return PLUGIN_USB_CONNECTED; break; case RET_VAL_QUIT_ERR: return PLUGIN_OK; break; } computer_allocate(); /* movement */ humanres.moves = superdom_settings.movesperturn; startmove: gamestate = GS_MOVE; switch(movement_menu()) { case RET_VAL_USB: return PLUGIN_USB_CONNECTED; break; case RET_VAL_QUIT_ERR: return PLUGIN_OK; break; } /* computer movement */ computer_movement(); /* spoil food */ if(superdom_settings.spoil_enabled) { spoil_food(); } /* feed men */ if(humanres.men) { if(humanres.food > humanres.men) { rb->snprintf(buf, sizeof(buf), "Your men ate %d units of food", humanres.men); humanres.food -= humanres.men; } else { rb->snprintf(buf, sizeof(buf), "There was not enough food" " to feed all your men, %d men have died of starvation", killmen(COLOUR_LIGHT)); } rb->splash(HZ*2, buf); } if(compres.men) { if(compres.food > compres.men) { compres.food -= compres.men; } else { rb->snprintf(buf, sizeof(buf), "The computer does not have" " enough food to feed its men. %d have died of starvation", killmen(COLOUR_DARK)); rb->splash(HZ, buf); } } /* war */ humanres.moves = superdom_settings.movesperturn; startwar: gamestate = GS_WAR; switch(war_menu()) { case RET_VAL_USB: return PLUGIN_USB_CONNECTED; break; case RET_VAL_QUIT_ERR: return PLUGIN_OK; break; } compres.moves = superdom_settings.movesperturn; computer_war(); gen_resources(); } return PLUGIN_OK; }