回精華區
作者itoc, 信區: itoc
標題股市大亨 -- stock.c
時間04/02/03
作者: itoc (League) 看板: plan
標題: 股市大亨
時間: 2004/02/03 Tue 14:49:04                           Updated: 2004/03/02

: src/maple/menu.c 適當的選單加入

  "bin/stock.so:main_stock", PERM_BASIC, - M_GAME,
  "1Stock     ♂ 股市大亨 ♀",

: src/include/config.h

+ #define STOCKSHM_KEY    4999    /* 股市 */
#define PIPSHM_KEY      4998    /* 電子雞對戰 */

: src/game/Makefile

SO =     ..... stock.so

: src/game/stock.c 新增此程式

/*-------------------------------------------------------*/
/* stock.c        ( NTHU CS MapleBBS Ver 3.10 )          */
/*-------------------------------------------------------*/
/* target : 真實股票遊戲                                 */
/* create : 98/06/21                                     */
/* update : 01/05/08                                     */
/* author : dsyan.bbs@forever.twbbs.org                  */
/* recast : itoc.bbs@bbs.tnfsh.tn.edu.tw                 */
/*-------------------------------------------------------*/


#if 0

 本股市需要配合 bin/stock-open.pl 這程式去抓資料,crontab 如下:

# 每天 3:50PM 轉換股市格式
50 15 * * * bin/stock-open.pl > etc/game/stock_data

 etc/game/stock_name 是所有股票的列表,若有新股加入,可以加在最後
 etc/game/stock_data 是所有股票的市價,stock-open.pl 會去抓

 要使用 stock-open.c 必須裝有 lynx。

#endif


#include "bbs.h"


#ifdef HAVE_GAME


#define STOSUM          2000       /* 遊戲中最多允許幾家公司 */
#define STOCKNAME       "股市大亨"

#define FN_STOCK        "stock"    /* usr 目錄下個人的股市檔案-各股擁有張數 */
#define FN_STOCK_LOG    "stock.log"/* usr 目錄下個人的股市檔案-歷史記錄 */


typedef struct
{
  char name[STOSUM][12];    /* 所有各家股票的名字 #### 甲乙丙
                               四個數字、一個空白、三個國字 */
  char update[11];          /* 更新日期格式 [90/05/08] */
  int price[STOSUM];        /* 價格 */
                            /* 為了程式方便,23.45 元就儲存成 23450,
                               即一張(1000股的價格) */
  int max;                  /* 共有幾家公司 */
  int maxpage;              /* 共需要幾頁 */
} STOCK;


int
main_stock()
{
  int own[STOSUM];      /* 對各股的擁有股份 */
  char *fname = FN_STOCK_LOG;
  char name[12], buf[128];
  FILE *fp;
  int i, ch, price, num, money, page, max, maxpage;
  time_t now;
  struct tm *ptime;
  STOCK *sto;

  if (HAS_STATUS(STATUS_COINLOCK))
  {
    vmsg(msg_coinlock);
    return XEASY;
  }

  if (!dashf("etc/game/stock_data") || !dashf("etc/game/stock_name"))
  {
    vmsg("股市檔案遺失,請告訴站長");
    return XEASY;
  }

  /* initialize shared memory */
  sto = shm_new(STOCKSHM_KEY, sizeof(STOCK));

  /* 讀資料檔 */

  fp = fopen("etc/game/stock_data", "r");   /* 讀取目前股價 */
  fgets(buf, 30, fp);
  if (strcmp(buf, sto->update))     /* 若 shm 中不是今天的資料,才更新 */
  {
    FILE *fn;

    strcpy(sto->update, buf);       /* 更新日期 */

    ch = 0;
    fn = fopen("etc/game/stock_name", "r");
    while (fgets(sto->name[ch], 12, fn))
    {
      fgets(buf, 12, fn);
      ch++;             /* 借用 ch 作為 stock_name 中的公司數 */
    }
    fclose(fn);

    num = 0;
    fn = fopen("etc/game/stock_name", "a");

    while (fgets(buf, 80, fp))  /* 從 stock_data 取出股價存入 shm */
    {
      /* 在本 while 迴圈中,
         ch 是指 stock_name 中的公司數,num 是指 stock_data 中的公司數 */

      if (buf[0] < '0' || buf[0] > '9')
        continue;       /* 不合理的格式 */

      buf[11] = '\0';
      buf[21] = '\0';
      num++;

      strcpy(name, buf);            /* buf[0] ~ buf[11] 為公司名稱 */
      price = atoi(buf + 16) * 10;  /* buf[16] ~ buf[21] 為價格 */

      /* 為了避免網頁的變動或新上市公司,而使 stock_name 和 stock_data
         不是一對一對應,是故從 stock_data 中抓到價格以後,要搜尋正確的欄位 */

      for (i = 0; i < ch; i++)
      {
        if (!strcmp(name, sto->name[i]))
          break;
      }

      if (i == ch && ch < STOSUM)   /* 如果有新增公司,要寫入 stock_name */
      {                 /* 如果已經超過 STOSUM,則不得增加 */
        strcpy(sto->name[i], name);
        fprintf(fn, "%s\n", name);
        ch++;
      }
      sto->price[i] = price;
    }

    fclose(fn);
    sto->max = num;
    sto->maxpage = num / 40 + ((num % 40) ? 1 : 0);
    /* maxpage = max/40 取天花板函數 */
  }
  fclose(fp);

  max = sto->max;
  maxpage = sto->maxpage;

  /* 讀取 user 資料檔 */

  usr_fpath(buf, cuser.userid, FN_STOCK);

  if (fp = fopen(buf, "r")) /* 以前玩過股市,讀取目前所有的股份 */
  {
    for (i = 0; i < STOSUM; i++)
      fscanf(fp, "%d\n", &own[i]);
  }
  else              /* 沒玩過股市,建立新檔 */
  {
    fp = fopen(buf, "w");
    for (i = 0; i < STOSUM; i++)
    {
      fprintf(fp, "%d\n", 0);
      own[i] = 0;
    }
  }
  fclose(fp);


  /* 畫面主程式 */

  page = 0;

  while (1)
  {
    vs_bar(STOCKNAME);
    move(1, 0);
    outs("\033[1;31;46m 編號 \033[42m 股 票 名 稱 \033[43m 價 格 \033[45m"
      " 持有張數 \033[46m     編號 \033[42m 股 票 名 稱 \033[43m"
      " 價 格 \033[45m 持有張數  \033[m");

    /* 將本頁的股價分成二欄顯示 */

    for (i = 0; i < 20; i++)
    {
      move(i + 2, 0);

      num = page * 40 + i;
      if (num + 1 <= max)
      {
        price = sto->price[num];
        ch = (price % 1000) / 10;   /* 小數位 */
        prints(" %3d)  %11s%5d.%02d %5d         ",
          num + 1, sto->name[num], price / 1000,
          (ch < 10 ? ch * 10 : ch), own[num]);
      }

      num += 20;
      if (num + 1 <= max)
      {
        price = sto->price[num];
        ch = (price % 1000) / 10;   /* 小數位 */
        prints("%3d)  %11s%5d.%02d %5d\n",
          num + 1, sto->name[num], price / 1000,
          (ch < 10 ? ch * 10 : ch), own[num]);
      }
    }

    move(b_lines - 1, 0);
    prints(COLOR1 " n:下頁 p:上頁 b:買 s:賣 v:看 e:修 q:離開 "
      "錢:%-8d %2d/%2d頁   %-12.10s\033[m",
      cuser.money, page + 1, maxpage, sto->update);

get_key:
    switch (ch = vkey())
    {

    /* 以下 name, price, num, i 將不再用到,借來做回答 */

    /* itoc.010512.註解: 由於是在下午三點半今天的盤已經關了以後,才去抓今天
       的收盤值,所以可以先知道實際今天各股的漲跌,如果不限制交易時間,那麼
       將可以以昨天的價格的買進。同理,賣出也要限制時間 */

    case 'b':   /* buy */
      now = time(0);
      ptime = localtime(&now);
      if (ptime->tm_hour >= 6 && ptime->tm_hour <= 15)
      {
        vmsg("交易時間為每天 4:00pm 到隔天早上 7:00am .. :)");
        break;
      }

      sprintf(buf, "要買哪家(1-%d)?", max);
      vget(b_lines, 0, buf, name, 4, DOECHO);
      i = atoi(name);
      if (i < 1 || i > max)
        break;

      price = sto->price[i - 1];
      price += price / 300; /* 抽稅千分之三 */
      money = price ? cuser.money / price : 0; /* itoc.010716: 預防 price=0 */
      sprintf(buf, "要買幾張(1-%d)?", money);
      vget(b_lines, 0, buf, name, 4, DOECHO);
      num = atoi(name);
      if (num < 1 || num > money)
        break;

      /* 買第 i 號股票 num 張 */
      own[i - 1] += num;
      money = num * price;
      if (cuser.money <= money)
        cuser.money = 0;
      else
        cuser.money -= money;

      /* 記錄購買記錄 */
      usr_fpath(buf, cuser.userid, fname);
      fp = fopen(buf, "a+");
      ch = (price % 1000) / 10; /* 小數位 */
      fprintf(fp, "[%s] %5d.%02d 買進 %s %d 張 花費 %d\n",
        Btime(&now), price / 1000, (ch < 10 ? ch * 10 : ch),
        sto->name[i - 1], num, money);
      fclose(fp);
      break;


    case 's':   /* sell */
      now = time(0);
      ptime = localtime(&now);
      if (ptime->tm_hour >= 3 && ptime->tm_hour <= 18)
      {
        vmsg("交易時間為每天 7:00pm 到隔天早上 4:00am .. :)");
        break;
      }

      sprintf(buf, "要賣哪家(1-%d)?", max);
      vget(b_lines, 0, buf, name, 4, DOECHO);
      i = atoi(name);
      if (i < 1 || i > max)
        break;
      price = own[i - 1];
      if (!price)       /* 沒有持股不能賣 */
        break;

      sprintf(buf, "要賣幾張(1-%d)?", price);
      vget(b_lines, 0, buf, name, 4, DOECHO);
      num = atoi(name);
      if (num < 1 || num > price)
        break;

      /* 賣第 i 號股票 num 張 */
      own[i - 1] -= num;
      price = sto->price[i - 1];
      price -= price / 300; /* 抽稅千分之三 */
      money = num * price;
      cuser.money += money;

      /* 記錄賣出記錄 */
      usr_fpath(buf, cuser.userid, fname);
      fp = fopen(buf, "a+");
      ch = (price % 1000) / 10; /* 小數位 */
      fprintf(fp, "[%s] %5d.%02d 賣出 %s %d 張 收入 %d\n",
        Btime(&now), price / 1000, (ch < 10 ? ch * 10 : ch),
        sto->name[i - 1], num, money);
      fclose(fp);
      break;


    case 'v':   /* view history */
      usr_fpath(buf, cuser.userid, fname);
      more(buf, NULL);
      break;


    case 'e':   /* edit history */
      usr_fpath(buf, cuser.userid, fname);
      vedit(buf, 0);
      break;


    case 'p':   /* previous page */
    case KEY_UP:
    case KEY_PGUP:
      if (page)
    page--;
      else
    page = maxpage - 1;
      break;


    case 'n':   /* next page */
    case KEY_DOWN:
    case KEY_PGDN:
      if (page + 1 < maxpage)
    page++;
      else
    page = 0;
      break;


    case 'q':
    case KEY_LEFT:
      /* 在離開股市時才回存所有股票張數及金錢,節省I/O */
      usr_fpath(buf, cuser.userid, FN_STOCK);
      fp = fopen(buf, "w");
      for (i = 0; i < STOSUM; i++)
       fprintf(fp, "%d\n", own[i]);
      fclose(fp);
      return 0;


    default:
      if (ch >= '1' && ch <= '9')
      {
        if (vget(b_lines, 0, "跳至第幾頁:", name, 3, DOECHO))
        {
          i = atoi(name) - 1;
          if (i < maxpage)
            page = i;
        }
        break;
      }
      goto get_key; /* 不用重繪畫面 */
    }
  }
}
#endif              /* HAVE_GAME */

--
□ 本文章由 itocpc512-2.EE.NCTU.edu.tw 發表
精華選讀←離開[主題上]主題下(k)上篇(j)下篇S/a搜尋 G串列 TAB精華 ↑↓捲 Pg/Space翻