Skip to content

Commit

Permalink
Add support for Ohtani DH rule
Browse files Browse the repository at this point in the history
This adjusts the statistical tabulation for starting pitchers who DH
for themselves.  These players are considered to have two distinct
identities within the game for lineup and substitution purposes.
We adopt the convention that batting and non-pitcher fielding statistics
are tabulated against the boxscore entry in the batting order, and
pitching and pitcher fielding statistics against the non-batting-order
identity.
  • Loading branch information
tturocy committed Apr 8, 2022
1 parent 5c27426 commit 4b3d779
Show file tree
Hide file tree
Showing 12 changed files with 63 additions and 33 deletions.
14 changes: 14 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# [0.9.3] - 2022-04-08

## Behaviour changes
- The new DH rule for the 2022 season allows a starting pitcher to DH for
himself. Per the text of the rule, such a player has two identities in the
lineup, one in his role as the DH in the batting order, and another as the
pitcher in batting order slot zero (in DiamondWare terminology).
DiamondWare files follow this convention by listing the player with two
start records. This version separates the statistical tabulation for the
two identities, with batting (and non-pitcher fielding) statistics for
such a player appearing in his record in the batting order, and his
pitching (and pitcher fielding) statistics appearing in his slot-zero entry.


# [0.9.2] - 2022-01-10

## New features
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
#


AC_INIT([chadwick],[0.9.2])
AC_INIT([chadwick],[0.9.3])
AC_CONFIG_SRCDIR([src/cwlib/chadwick.h])
AC_CONFIG_MACRO_DIR([m4])
AM_INIT_AUTOMAKE([foreign])
Expand Down
4 changes: 2 additions & 2 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@
# built documents.
#
# The short X.Y version.
version = '0.9.2'
version = '0.9.3'
# The full version, including alpha/beta/rc tags.
release = '0.9.2'
release = '0.9.3'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
22 changes: 14 additions & 8 deletions src/cwlib/box.c
Original file line number Diff line number Diff line change
Expand Up @@ -392,18 +392,22 @@ cw_box_add_substitute(CWBoxscore *boxscore, CWGameIterator *gameiter)
}

/*
* Find the boxscore entry for player with ID player_id
* Find the boxscore entry for player with ID player_id.
* If batter is nonzero, search only for batting entries; this implements
* the 'Ohtani rule' for DHes, in which a player who DHes for himself
* has two identities within the game, one in the batting order and
* one as the DHed-for pitcher.
*/
CWBoxPlayer *
cw_box_find_player(CWBoxscore *boxscore, char *player_id)
cw_box_find_player(CWBoxscore *boxscore, char *player_id, int batter)
{
int i, t;

if (player_id == NULL) {
return NULL;
}
for (t = 0; t <= 1; t++) {
for (i = 0; i <= 9; i++) {
for (i = (batter) ? 1 : 0; i <= 9; i++) {
CWBoxPlayer *player = boxscore->slots[i][t];
while (player != NULL) {
if (!strcmp(player->player_id, player_id)) {
Expand Down Expand Up @@ -616,7 +620,8 @@ cw_box_batter_stats(CWBoxscore *boxscore, CWGameIterator *gameiter)
player = cw_box_find_player(boxscore,
cw_gamestate_charged_batter(gameiter->state,
gameiter->event->batter,
event_data));
event_data),
1);
if (cw_event_is_batter(event_data) && player == NULL) {
/* If not a batter event, we will be tolerant if the player ID
* in the batter field is bogus.
Expand Down Expand Up @@ -1319,7 +1324,8 @@ cw_box_process_boxscore_file(CWBoxscore *boxscore, CWGame *game)
team = cw_data_get_item_int(stat, 2);
seq = cw_data_get_item_int(stat, 3);
pos = cw_data_get_item_int(stat, 4);
player = cw_box_find_player(boxscore, stat->data[1]);
player = cw_box_find_player(boxscore, stat->data[1],
(pos != 1) ? 1 : 0);
if (player == NULL) {
fprintf(stderr,
"ERROR: In %s, cannot find entry for player '%s' listed in dline.\n",
Expand Down Expand Up @@ -1348,7 +1354,7 @@ cw_box_process_boxscore_file(CWBoxscore *boxscore, CWGame *game)
}
else if (!strcmp(stat->data[0], "phline")) {
team = cw_data_get_item_int(stat, 3);
player = cw_box_find_player(boxscore, stat->data[1]);
player = cw_box_find_player(boxscore, stat->data[1], 1);
if (player == NULL) {
fprintf(stderr,
"ERROR: In %s, cannot find entry for player '%s' listed in phline.\n",
Expand All @@ -1359,7 +1365,7 @@ cw_box_process_boxscore_file(CWBoxscore *boxscore, CWGame *game)
}
else if (!strcmp(stat->data[0], "prline")) {
team = cw_data_get_item_int(stat, 3);
player = cw_box_find_player(boxscore, stat->data[1]);
player = cw_box_find_player(boxscore, stat->data[1], 1);
if (player == NULL) {
fprintf(stderr,
"ERROR: In %s, cannot find entry for player '%s' listed in prline.\n",
Expand Down Expand Up @@ -1487,7 +1493,7 @@ cw_box_create(CWGame *game)
if (pitcher != NULL) pitcher->pitching->sv = 1;
}
if (cw_game_info_lookup(game, "gwrbi") != NULL) {
batter = cw_box_find_player(boxscore, cw_game_info_lookup(game, "gwrbi"));
batter = cw_box_find_player(boxscore, cw_game_info_lookup(game, "gwrbi"), 1);
if (batter != NULL) batter->batting->gw = 1;
}
return boxscore;
Expand Down
3 changes: 2 additions & 1 deletion src/cwlib/box.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ CWBoxPitcher *cw_box_get_starting_pitcher(CWBoxscore *boxscore, int team);
/*
* Find the player entry for player with ID player_id
*/
CWBoxPlayer *cw_box_find_player(CWBoxscore *boxscore, char *player_id);
CWBoxPlayer *cw_box_find_player(CWBoxscore *boxscore, char *player_id,
int batter);

/*
* Find the pitching entry for player with ID player_id
Expand Down
9 changes: 7 additions & 2 deletions src/cwlib/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,19 @@ char *cw_strtok(char *strToken)
* for invalid values.
*/
int
cw_atoi(char *s)
cw_atoi(char *s, char *msg)
{
char *end = NULL;
long temp = strtol(s, &end, 10);
if (end != s && errno != ERANGE && temp >= INT_MIN && temp <= INT_MAX) {
return (int) temp;
}
fprintf(stderr, "Warning: Invalid integer value '%s'\n", s);
if (msg != NULL) {
fprintf(stderr, msg, s);
}
else {
fprintf(stderr, "Warning: Invalid integer value '%s'\n", s);
}
return -1;
}

Expand Down
4 changes: 3 additions & 1 deletion src/cwlib/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ char *cw_strtok(char *strToken);
/*
* A replacement for C atoi(), which does validity checking and returns
* -1 as the "null" value for invalid inputs.
* If 'msg' is specified and not null, it is used as the format string
* to print a warning message.
*/
int cw_atoi(char *s);
int cw_atoi(char *s, char *msg);

/*
* Searches for the game 'game_id' in 'file'; sets the file pointer to
Expand Down
20 changes: 11 additions & 9 deletions src/cwlib/game.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ int cw_data_get_item_int(CWData *data, unsigned int index)
if (index >= data->num_data) {
return -1;
}
return cw_atoi(data->data[index]);
return cw_atoi(data->data[index], NULL);
}

CWGame *cw_game_create(char *game_id)
Expand Down Expand Up @@ -726,8 +726,8 @@ cw_game_read(FILE *file)
pos = cw_strtok(NULL);
if (player_id && name && team && slot && pos) {
cw_game_starter_append(game, player_id, name,
cw_atoi(team), cw_atoi(slot),
cw_atoi(pos));
cw_atoi(team, NULL), cw_atoi(slot, NULL),
cw_atoi(pos, NULL));
}
}
else if (!strcmp(tok, "play")) {
Expand All @@ -739,7 +739,9 @@ cw_game_read(FILE *file)
pitches = cw_strtok(NULL);
play = cw_strtok(NULL);
if (inning && batting_team && batter && count && pitches && play) {
cw_game_event_append(game, cw_atoi(inning), cw_atoi(batting_team),
cw_game_event_append(game,
cw_atoi(inning, NULL),
cw_atoi(batting_team, NULL),
batter, count, pitches, play);
}
if (batHand != ' ' && !strcmp(batHandBatter, batter)) {
Expand Down Expand Up @@ -781,8 +783,8 @@ cw_game_read(FILE *file)
pos = cw_strtok(NULL);
if (player_id && name && team && slot && pos) {
cw_game_substitute_append(game, player_id, name,
cw_atoi(team), cw_atoi(slot),
cw_atoi(pos));
cw_atoi(team, NULL), cw_atoi(slot, NULL),
cw_atoi(pos, NULL));
}
}
else if (!strcmp(tok, "com")) {
Expand Down Expand Up @@ -863,8 +865,8 @@ cw_game_read(FILE *file)
align = cw_strtok(NULL);
slot = cw_strtok(NULL);
if (align && slot) {
ladjAlign = cw_atoi(align);
ladjSlot = cw_atoi(slot);
ladjAlign = cw_atoi(align, NULL);
ladjSlot = cw_atoi(slot, NULL);
}
}
else if (!strcmp(tok, "cw:itb") | !strcmp(tok, "radj")) {
Expand All @@ -878,7 +880,7 @@ cw_game_read(FILE *file)
base = cw_strtok(NULL);
if (runner && base) {
strncpy(autoRunner, runner, 255);
autoBase = cw_atoi(base);
autoBase = cw_atoi(base, NULL);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/cwtools/cwboxsml.c
Original file line number Diff line number Diff line change
Expand Up @@ -1402,8 +1402,8 @@ cwbox_event_metadata(XMLNode *parent, CWGame *game)
}

xml_node_attribute_int(node, "game-of-day",
(cw_atoi(cw_game_info_lookup(game, "number")) == 0) ? 1 :
cw_atoi(cw_game_info_lookup(game, "number")));
(cw_atoi(cw_game_info_lookup(game, "number"), NULL) == 0) ? 1 :
cw_atoi(cw_game_info_lookup(game, "number"), NULL));

if (cw_game_info_lookup(game, "htbf") &&
!strcmp(cw_game_info_lookup(game, "htbf"), "true")) {
Expand Down
2 changes: 1 addition & 1 deletion src/cwtools/cwdaily.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ DECLARE_FIELDFUNC(cwdaily_number)
char *tmp;
return sprintf(buffer, (ascii) ? "%d" : "%5d",
(tmp = cw_game_info_lookup(gameiter->game, "number")) ?
cw_atoi(tmp) : 0);
cw_atoi(tmp, NULL) : 0);
}

/* Field 3 */
Expand Down
10 changes: 5 additions & 5 deletions src/cwtools/cwgame.c
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ DECLARE_FIELDFUNC(cwgame_number)
char *tmp;
return sprintf(buffer, (ascii) ? "%d" : "%5d",
(tmp = cw_game_info_lookup(gameiter->game, "number")) ?
cw_atoi(tmp) : 0);
cw_atoi(tmp, NULL) : 0);
}

/* Field 3 */
Expand Down Expand Up @@ -371,7 +371,7 @@ DECLARE_FIELDFUNC(cwgame_attendance)
char *tmp;
return sprintf(buffer, (ascii) ? "%d" : "%5d",
(tmp = cw_game_info_lookup(gameiter->game, "attendance")) ?
cw_atoi(tmp) : 0);
cw_atoi(tmp, "Warning: invalid value '%s' for info,attendance") : 0);
}

/* Field 19 */
Expand Down Expand Up @@ -452,7 +452,7 @@ DECLARE_FIELDFUNC(cwgame_temperature)
char *tmp;
return sprintf(buffer, (ascii) ? "%d" : "%3d",
(tmp = cw_game_info_lookup(gameiter->game, "temp")) ?
cw_atoi(tmp) : 0);
cw_atoi(tmp, NULL) : 0);
}

/* Field 27 */
Expand All @@ -475,7 +475,7 @@ DECLARE_FIELDFUNC(cwgame_wind_speed)
char *tmp;
return sprintf(buffer, "%d",
(tmp = cw_game_info_lookup(gameiter->game, "windspeed")) ?
cw_atoi(tmp) : 0);
cw_atoi(tmp, NULL) : 0);
}

/* Field 29 */
Expand Down Expand Up @@ -523,7 +523,7 @@ DECLARE_FIELDFUNC(cwgame_time_of_game)
char *tmp;
return sprintf(buffer, (ascii) ? "%d" : "%3d",
(tmp = cw_game_info_lookup(gameiter->game, "timeofgame")) ?
cw_atoi(tmp) : 0);
cw_atoi(tmp, NULL) : 0);
}

/* Field 33 */
Expand Down
2 changes: 1 addition & 1 deletion src/python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

setup(name="chadwick",
description="A library for manipulating baseball game-level and play-level data",
version="0.9.1",
version="0.9.3",
author="Dr T L Turocy",
author_email="ted.turocy@gmail.com",
url="http://chadwick.sourceforge.net",
Expand Down

0 comments on commit 4b3d779

Please sign in to comment.