From 4ff43893d0aabb4589a0eb618fff29cbee49628a Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sat, 7 May 2022 21:57:15 -0700 Subject: [PATCH] VASTLY improved how I generate tiles and tiledata --- .gitignore | 170 +-- archive/main.c | 235 ++++ assets/images/border.png | Bin 175 -> 240 bytes assets/images/cards_tiles.png | Bin 0 -> 832 bytes assets/images/cards_tiles.pxo | Bin 0 -> 1273 bytes assets/images/font2.aseprite | Bin 667 -> 0 bytes assets/images/tileset.png | Bin 0 -> 120 bytes assets/images_noconvert/FINISHEDV2.png | Bin 0 -> 11394 bytes assets/images_noconvert/cards.png | Bin 0 -> 13697 bytes assets/images_noconvert/cards.pxo | Bin 0 -> 25118 bytes assets/images_noconvert/tileset.png | Bin 0 -> 120 bytes assets/tileset.png | Bin 119 -> 0 bytes package.json | 34 +- scripts/351.js | 8 +- scripts/build.js | 246 ++-- scripts/clean.js | 6 +- scripts/common.js | 12 +- scripts/gb2png.js | 174 +-- scripts/gbc_old.js | 492 ++++---- scripts/png2gb.js | 192 +-- scripts/string2gb.js | 62 +- scripts/util.js | 40 +- src/conversation/fade.c | 136 +- src/conversation/fade.h | 40 +- src/conversation/frame.c | 40 - src/conversation/frame.h | 28 - src/conversation/pause.c | 42 +- src/conversation/pause.h | 26 +- src/conversation/questionbox.c | 172 +-- src/conversation/questionbox.h | 50 +- src/conversation/queue.c | 628 +++++----- src/conversation/queue.h | 52 +- src/conversation/textbox.c | 292 +++-- src/conversation/textbox.h | 93 +- src/display/common.c | 19 - src/display/common.h | 37 - src/display/tilemap.h | 15 - src/input.c | 22 +- src/input.h | 24 +- src/libs.h | 28 +- src/main.c | 296 ++--- src/main.h | 54 +- src/poker/card.c | 98 +- src/poker/card.h | 232 ++-- src/poker/player.h | 54 +- src/poker/poker.c | 1576 ++++++++++++------------ src/poker/poker.h | 112 +- src/poker/pot.h | 52 +- src/poker/turn.h | 58 +- src/poker/winner.h | 126 +- src/sprites/spriteborder.c | 44 + src/sprites/spriteborder.h | 39 + src/sprites/spritecards.c | 38 + src/sprites/spritecards.h | 68 + src/sprites/spritefont.c | 16 + src/sprites/spritefont.h | 30 + src/sprites/sprites.c | 12 + src/sprites/sprites.h | 18 + src/sprites/spritetileset.c | 12 + src/sprites/spritetileset.h | 50 + src/strings.c | 64 +- src/strings.h | 66 +- src/time.c | 42 +- src/time.h | 48 +- src/util.h | 24 +- test.sh | 14 +- 66 files changed, 3472 insertions(+), 3186 deletions(-) create mode 100644 archive/main.c create mode 100644 assets/images/cards_tiles.png create mode 100644 assets/images/cards_tiles.pxo delete mode 100644 assets/images/font2.aseprite create mode 100644 assets/images/tileset.png create mode 100644 assets/images_noconvert/FINISHEDV2.png create mode 100644 assets/images_noconvert/cards.png create mode 100644 assets/images_noconvert/cards.pxo create mode 100644 assets/images_noconvert/tileset.png delete mode 100644 assets/tileset.png delete mode 100644 src/conversation/frame.c delete mode 100644 src/conversation/frame.h delete mode 100644 src/display/common.c delete mode 100644 src/display/common.h delete mode 100644 src/display/tilemap.h create mode 100644 src/sprites/spriteborder.c create mode 100644 src/sprites/spriteborder.h create mode 100644 src/sprites/spritecards.c create mode 100644 src/sprites/spritecards.h create mode 100644 src/sprites/spritefont.c create mode 100644 src/sprites/spritefont.h create mode 100644 src/sprites/sprites.c create mode 100644 src/sprites/sprites.h create mode 100644 src/sprites/spritetileset.c create mode 100644 src/sprites/spritetileset.h diff --git a/.gitignore b/.gitignore index 2967922..5f01450 100644 --- a/.gitignore +++ b/.gitignore @@ -1,84 +1,86 @@ -# Prerequisites -*.d - -# Object files -*.o -*.ko -build/*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -# CMake -CMakeLists.txt.user -CMakeCache.txt -CMakeFiles -CMakeScripts -Testing -Makefile -cmake_install.cmake -install_manifest.txt -compile_commands.json -CTestTestfile.cmake -_deps - -# Custom -build -.vscode - -assets/testworld/tileset.png -oldsrc - -node_modules -yarn.lock - -*.log -obj -res - -out.c -out.c.png - -emulator \ No newline at end of file +# Prerequisites +*.d + +# Object files +*.o +*.ko +build/*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +# Custom +build +.vscode + +assets/testworld/tileset.png +oldsrc + +node_modules +yarn.lock + +*.log +obj +res + +out.c +out.c.png + +emulator + +bgb/* \ No newline at end of file diff --git a/archive/main.c b/archive/main.c new file mode 100644 index 0000000..a19f019 --- /dev/null +++ b/archive/main.c @@ -0,0 +1,235 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "main.h" + +/* + +inline uint8_t mainGetChar(char c) { + return c - TEXTBOX_FONT_START + FONT_DATA_POSITION; +} + +inline void mainBufferCard(uint8_t card, uint8_t *tiles) { + uint8_t suit, number; + + if(card >= CARD_DECK_SIZE) { + tiles[0] = mainGetChar('N'); + tiles[1] = mainGetChar('A'); + return; + } + + suit = cardGetSuit(card); + number = cardGetNumber(card); + + switch(suit) { + case CARD_SUIT_CLUBS: + tiles[0] = mainGetChar('C'); + break; + case CARD_SUIT_DIAMONDS: + tiles[0] = mainGetChar('D'); + break; + case CARD_SUIT_HEARTS: + tiles[0] = mainGetChar('H'); + break; + case CARD_SUIT_SPADES: + tiles[0] = mainGetChar('S'); + break; + } + + switch(number) { + case CARD_TWO: + tiles[1] = mainGetChar('2'); + break; + case CARD_THREE: + tiles[1] = mainGetChar('3'); + break; + case CARD_FOUR: + tiles[1] = mainGetChar('4'); + break; + case CARD_FIVE: + tiles[1] = mainGetChar('5'); + break; + case CARD_SIX: + tiles[1] = mainGetChar('6'); + break; + case CARD_SEVEN: + tiles[1] = mainGetChar('7'); + break; + case CARD_EIGHT: + tiles[1] = mainGetChar('8'); + break; + case CARD_NINE: + tiles[1] = mainGetChar('9'); + break; + case CARD_TEN: + tiles[1] = mainGetChar('T'); + break; + case CARD_JACK: + tiles[1] = mainGetChar('J'); + break; + case CARD_QUEEN: + tiles[1] = mainGetChar('Q'); + break; + case CARD_KING: + tiles[1] = mainGetChar('K'); + break; + case CARD_ACE: + tiles[1] = mainGetChar('A'); + break; + } +} + +inline void mainDebugDraw() { + uint8_t j, i, n; + + // DEBUG DRAW + uint8_t tiles[15]; + char buffer[6]; + buffer[0] = '\0'; + + for(j = 0; j < POKER_PLAYER_COUNT; j++) { + n = 0; + + if(j == POKER_PLAYER_BETTER) { + tiles[n++] = mainGetChar('>'); + } else { + tiles[n++] = COMMON_TILE_3; + } + + mainBufferCard(POKER_PLAYERS[j].hand[0], tiles+n); + n+=2; + mainBufferCard(POKER_PLAYERS[j].hand[1], tiles+n); + n+=2; + + tiles[n++] = COMMON_TILE_3; + + if((POKER_PLAYERS[j].state & POKER_PLAYER_STATE_FOLDED) == 0) { + tiles[n++] = COMMON_TILE_3; + } else { + tiles[n++] = mainGetChar('F'); + } + + if((POKER_PLAYERS[j].state & POKER_PLAYER_STATE_HAS_BET_THIS_ROUND) == 0) { + tiles[n++] = COMMON_TILE_3; + } else { + tiles[n++] = mainGetChar('H'); + } + + if((POKER_PLAYERS[j].state & POKER_PLAYER_STATE_OUT) == 0) { + tiles[n++] = COMMON_TILE_3; + } else { + tiles[n++] = mainGetChar('O'); + } + + tiles[n++] = COMMON_TILE_3; + + // Print chips + sprintf(buffer, "%u", (uint16_t)POKER_PLAYERS[j].chips); + for(i = 0; i < strlen(buffer); i++) { + tiles[n++] = mainGetChar(buffer[i]); + } + + while(n < 15) tiles[n++] = COMMON_TILE_3; + set_bkg_tiles(0x00, j, n - 1, 1, tiles); + } + + for(j = 0; j < POKER_COMMUNITY_SIZE_MAX; j++) { + if(j >= POKER_COMMUNITY_SIZE) { + tiles[j*2] = COMMON_TILE_3; + tiles[(j*2) + 1] = COMMON_TILE_3; + } else { + mainBufferCard(POKER_COMMUNITY[j], tiles + (j * 2)); + } + } + + set_bkg_tiles(0x00, POKER_PLAYER_COUNT + 1, POKER_COMMUNITY_SIZE_MAX * 2, 1, tiles); + + + // Print Pot + n = 0; + sprintf(buffer, "%u", (uint16_t)POKER_POTS[POKER_POT_CURRENT].chips); + for(i = 0; i < strlen(buffer); i++) { + tiles[n++] = mainGetChar(buffer[i]); + } + while(n < 5) tiles[n++] = COMMON_TILE_3; + set_bkg_tiles(0x00, POKER_PLAYER_COUNT + 2, n, 1, tiles); +} + +*/ + +void main() { + int16_t j; + uint8_t filled[GB_BACKGROUND_COLUMNS*GB_BACKGROUND_ROWS]; + + // Set up the GAMEBOY's registers. + disable_interrupts(); + DISPLAY_OFF; + LCDC_REG = LCDCF_OFF | LCDCF_BG8000 | LCDCF_BG9800 | LCDCF_BGON; + // Set the background color palette register + BGP_REG = OBP0_REG = OBP1_REG = 0xE4U; + + // Init the random seed + initarand(DIV_REG); + + // Init things + spriteTilesetBuffer(0x00); + spriteFontBuffer(TEXTBOX_SPRITE_FONT_POSITION); + spriteBorderBuffer(TEXTBOX_SPRITE_BORDER_POSITION); + spriteCardsBuffer(TEXTBOX_SPRITE_BORDER_POSITION + BORDER_IMAGE_TILES); + + conversationTextboxInit(); + conversationQueueInit(); + pokerInit(); + + // Fill screen white + for(j = 0; j < GB_BACKGROUND_COLUMNS * GB_BACKGROUND_ROWS; j++) filled[j] = TILESET_WHITE; + set_bkg_tiles(0x00, 0x00, GB_BACKGROUND_COLUMNS, GB_BACKGROUND_ROWS, filled); + SCX_REG = 0x00; + SCY_REG = 0x00; + + // Card Test + uint8_t cardTiles[4 * 6]; + spriteCardBufferTiles( + TEXTBOX_SPRITE_BORDER_POSITION+BORDER_IMAGE_TILES, + cardTiles, + CARD_HEARTS_TWO + ); + set_bkg_tiles(0x00, 0x00, 4, 6, cardTiles); + + // Now turn the screen on + DISPLAY_ON; + enable_interrupts(); + wait_vbl_done(); + + // Begin game + conversationQueueNext(); + + // Begin the loop + while(1) { + // Perform non-graphical code updates + wait_vbl_done(); + + // Update the input state + INPUT_LAST = INPUT_STATE; + INPUT_STATE = joypad(); + INPUT_PRESSED = (~INPUT_LAST) & INPUT_STATE; + + // Tick time. + timeUpdate(); + + // Update conversation pause effect + conversationPauseUpdate(); + + // Update question box and textbox + questionBoxUpdate(); + conversationTextboxUpdate(); + + // Update conversation fade effect + conversationFadeUpdate(); + // mainDebugDraw(); + } +} \ No newline at end of file diff --git a/assets/images/border.png b/assets/images/border.png index 24ef98bf3b1c9fa5abb735dfb04373df45b32d45..05c2b8fae38a87260b8ec3d873817f1523d16b41 100644 GIT binary patch delta 225 zcmV<703QFZ0q_Bk7k>~41^@s63?#pm00001b5ch_0Itp)=>Px#s!2paR5*>TR5=cS zFbs1MJD+7_WaI~Yg}=doEQK_OO6!G&xK5KcDd)A^7=U#Hj{W@t?-~H65#kaQCT4kp z9wBPz|DO!#JoYb9+Lo{m;s&78$|myZ1G)huK?=!rmO)q|Nm^i8dddTcOZEsYev32P zw03yeRd*1TA@f_PGRR(%4v>}2e5OF%2a#aZH*TX@B9@P~oc)bJ%GXn#|^_6E&0V&4fAa^H*b?0PW0y#VZJ|V8N_waYG;WtP!;J4ua z;oaU06c6-taSYLzcs4Mai$Q@$XwUz-++o!l1;0*B7U+-?u3+HiFl7J2z3Py~A?-~U z)vhI{ABtXjenn`=hm5HlbrUbPmrK4Ce0uGcyVj{+9Ild6uf`=YY!qYiUALQ28fZF$ Mr>mdKI;Vst04?G=2mk;8 diff --git a/assets/images/cards_tiles.png b/assets/images/cards_tiles.png new file mode 100644 index 0000000000000000000000000000000000000000..c3101c50d33a9cddc65a9a5a49d7c50539548310 GIT binary patch literal 832 zcmV-G1Hb%Px%`bk7VRCt{2nY(%0Kn#FEeyYtG>?u4zmT>wsajR0LO0`y_URlBec!IQ=!Crta zoZ|;Vc-=#?&EQudkvQA~hXX*WJ3J5LdIKWAA8ug(=S`IFwg8&`SC@Cg{V?vfa)b5e zqm!k|SNP)Tcngm|Pt)%Y=GUci+D6isry#3E+?n=Bz;M4U8j3g z-s1+?N*r_%oZQmK-y@7g`lWFCGXq$Q)8v*u-91UJEz*)(bL3uH4!SJ;T&6VWNkP}3L!bTe z?o2+C4@XX*b=LvcM%s*InEmli^$x8H#PsZ4gBCei!zy+>?Evi54!{_|n@c}q@g-RE z><-TZe~<*|1W$iYY#ydMwPiLX1?Su}i}lu*tZ+SrS6iGMfpH+f* zV%}WJAr)3uiENG0`q^Su2?EG@wq&;@1C}lxBkgW(jOaZB*SI<@M(E-_#R|of0=+R} zdM)0nvZDi+=4p>UHiR~R%_Gbpm|V(oIcegSJ_S#-#SW)-Qv0ebz(EU*nm#n6d!X$& zJ<$L(n4}3wJ5A;EoJ#uM$y@hJ_06}!mJ3O8d;~OoiEF(|Gmik?K@8_1Ir^n|V}#U> z>WCh|b#5)9R_=v*m#3XHwt58c0Gx|!XP)YnKE8V9=4mGxVftUd`)ZsH{QB~G*|(M# zrY{Y0OJNoStwmB#`HVk3TGI{p=dZ-@!Z@Sgk zCZ#V)z7%FLz$!2}BmDaDy<y^mPgZSN$nlw15DyY001B%ABB%!=f1wX}(B1Ct$Cz$m~0 zGypaLHUQD{bWL$BV6?4VH$309vYH8R_}=+L+1a)oS=zM?8-7PHMS@Y=R+7(YQ;s83 z3A=i8BLPn{I1=vaiGF+js9nnl79x$dPP;N(ydNCj;@|GPASfUt2q!G=$mgTQJMssi zldMZe87g*8lo5`Z;q06;xBR1TC$fP~T3C-fYtgj~Z1t$e5Hd)!kwtKK!y z=9x~9qHX=>@RX4$v@3xcs3A)rfrx@61qGGLa1jOuDVA0T5}-o}LP!XrL_oMe1SUj* z1f)b61xZ>1i}#eD0~EWJN}2ipHkW0D_lZYx;XUbI%txM%Z`@FokIyxhgN8bNjQ-`D zV5M^mbN2x8t6cbgdWZAd+?Oxdv;OTDkJ{X9XJ5flSn9+5j!SeR9AV{Tf8F=StTlY{ zhja5H{oXR~9$?Rgz03D8HaPzd-@DCsDj)RI%+BxLyqn~t>;J!pgQtK12j*}u35+3^ za2)y5a5=<7u>;A2&!`UI*~z6J|Nq1&(u3dtqHu)9$W3{ik$Y!7 z(1F_s<(1_}4;{3MCK(&fVNUMuGd)Qva*}J4%+4LR2OP}vL=JX-v^%ZT zhc(ZWw@2Q28w~A@_cq_~*M3HZxcMbJTFNAh2xF`-#skI=g0+xFkG_M9{ik7YnE3I> zP!gW!lQ%`WOI!RVS1Np8Ak^oHtxoWs9L!V!D77#BU;qyl3jlZz|C_uII51)`o4gPI z4>*AdsNoA8!372}Dc1uMU=k<_w1fyi0TE!K3M-_fpfGI?tPRLdSjlvr>XmR=sq3-& zC^I#1b&i+yQFHUY=*ly04=gXUoRI-Rq~&>auo$uzC@=QP2h<7NcB-^4(#BA#!mO_!-cg@!(cqqWEh;a?JmY!-xmz}5)J!F>ptCoAO88s zb#OpNXna$)@jVK(&4l1Ge2k zD}=^b;WzZxRb3hi|Bs+y@Kv2p*aB{SjSURG)fAHq3w+ANANvwKc^}nU7D77#BU;qyV1pr_V{|`71|C_upVlV(( zsNfkk1HghIA*wnOU=tt&q=XO_5Tb<^N<0#eP{wQHMeF%4fE>3EhOy=Vg&F5Rfa`sK zdIQ9%y%Y8WyjMZ+>I19mk*uDjiFl2AUZ-WCqysqTp_%IDIX=xRkLJ~D0zCW;+We|8y|G;eB)tsC@`y$bWoq^tYL8Qb@IkhObg&}o@xfp4k-6PPXQ>kFa2Nu4^037 j5C8xH0Q>g=7AUnZ{a_XWO#lE8000315h{QMM?+9XDy~z^ literal 0 HcmV?d00001 diff --git a/assets/images/font2.aseprite b/assets/images/font2.aseprite deleted file mode 100644 index 00f4f16453d707139d96f1815336bcbe7f85bde0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 667 zcmbQu#K7=iDI-Gw5OOduGB7Zt05JlvFff7yfihqUAweaUG|>KEA6db+vI5N%17das zbi0gzqLK<9B>64)|AWi{x*bRW<$;nw8KAHdNK}Cz zNk33I0~do+VsdtRQGRJ&N;gmos{%y9f1ukS02DeEbKZvBoOIZLBYDmL|IgPt%BbD+ zv97jRnECd|F((;AF&1x@-|3&Tr`RUhzju9e@l1)cu0`T)#r{J)3pZ5eF50W-xJ>P( z-p?x+vzhMBzI=h*(05C4*MyGzp9#Oi?JSpkHaOJds1je{eoXrgLuF$AyA7XO?UtL` znc8n@{<-;zlklZSx;6h6t-PFL|8n)$rCdvUdMga=u1((=v7bAUbr0W5j=wXLryZ6r zy3VvUZnM>f`A5P}>A8DP%++mK+IPmovi!qM_JTPP_td*jsh%@(c{%Z#m-kyQwK}V2S^a4D%Jm-W^$S#=YcB6a`( literal 0 HcmV?d00001 diff --git a/assets/images_noconvert/FINISHEDV2.png b/assets/images_noconvert/FINISHEDV2.png new file mode 100644 index 0000000000000000000000000000000000000000..a711243e3d1c823b211e96f3bbf540fae18e47c3 GIT binary patch literal 11394 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 A;{X5v literal 0 HcmV?d00001 diff --git a/assets/images_noconvert/cards.pxo b/assets/images_noconvert/cards.pxo new file mode 100644 index 0000000000000000000000000000000000000000..4438902e76c072d36b288c2f22df841c20ab3bc8 GIT binary patch literal 25118 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 FtaLe!4wj9$c zfrb`Oa#?h{qhjacoTFm-b)w)xaYhpUI*yMNC`S;L2lfG8YT1b+RomY%iX-yv!4X7`C7?dQwSkrQ=%(ctRIPkUSk zj2{xNKaK_Ql(4P%>4)i_!lL#9{Mc%a=!9xvPDHqj^A3^C5qHyqAh#6icCS*Jbc^|= z)2|&ajq300AyCI-Rvr;J_~T5NK2ATue=P6w-W0mtjnrpSuiVr()`UF_p}{~ZYBUtv zi5o{u--`Hb60GF&Lwwn zeZ;^!jVGD`zO{T(g%0DmJ?3Qb+OmOW5w}{yEC*%p=4{j)tCFV*cOiAVT_N+0xmgXN zz+5Ahi-d)1udohOYr?|LcHp15AqcT!Q*zw?(6%}H2UsH#p5+h0KyD0|}K@K>2opHN2w4{J1E@TF<=j zNl=LdLo>9+9U5xVtvve`?LO~F+vGA1W~>!8GDzWb?#LcC4F*g{*pz#ECR*?K zPIAH4E-}!_O4b7dwYG^%=x@vCFHwq5sxFB*s|d$h!v`Jci*)Dq)%^M{2>uE6b0dEP zhtMjf*tqw3D>SXV_U}7=pu5r=u-~_7koo#wH3Ho0 z{~O)>A(tMP$QVFJk~xlZh?anlUxsFu#4w%Dq|^eURFbdQxrQfnoT@TvHdh@?J?rJg z4kn~6^R448dl`n?w`f#hgxp1a1={@Q1vUo^ z@DkqfbwzZ2Pizc3OewVd#ks3|(bYoEPCC5S{pMz*s*kjzy+&D)9pU=e=2KFLZ)r-F z<>|Pt`9z7z)x@}tWgq8#y2jYNhYz1#kaVWjQlOLfNglM4NbNc3{xeW#+o|EBLgZVl z3UNTHBK*GB<@de2gF?+93{xLUf-j)G$?XQr^ILqNiIH+3roZQ^=KdR0q7VFOu%eHxLBQEU0|&&7kUItB*ECg`%}=Y4*ck=rW1n~3KaoU;XsbhomAqMaN zEb};tTAX+o%jj$QcuO>7eo(rVVtm?mYKb?^WF*ziZ&Zpse1PZMg-QPy5%&Rt!lYi6 zdz$-;ta^gV@bkJv&$}9w!uT_LwDLLNC07rfa%+w(f|R5kM~#^v&3-r)r%+W0j}EYN zX2cSm7PX;$)x+GUwi-TdxvpeOx?aY5L|2j)AAEZn87|Q4Je|JtfLif)pXCCy$iGzi zXTJ%1Ko9|F5%PGlG=3Hv42zEDGm2}^lVM=9O-%KpMZtvS$>@h~==QfdN1tVQFj&-h z+N#55>w+@mM9mzZlDyYYFK4>nsyAU3RmJ7oJ6;gWZ7@&gav*oKEI74diZg?_Ah#}P zM}ToLbUo^PHE$Nh_QcY5reG%I(Hk1fPI87vDZWF6xKEi2? ztUc&qnV>PCvbvM>OAtNr&+Y-1Ss?buDg$=$;EyUZ625Uk6E;{B&L){UvihfJ${x&DW9!7dBRqR|;ojUcU8Fhh97HV6X*!b` zFEVk9_txRVH&dR_M+XHyR$)sndG^F0pG`h$hIRWAiITfwP?R~voL(pZ%B7V8oAD;yB?OOwr> zv$Js%Ul>qlmmF}*bDkKDTbom+OQ1vB^7tQI?DPJ5 zBsc!sl0p?~TWDULr>v;tr*%JJd>%!J%&CWauE^D2xi7$zpw z=Ez_^WY$2MlMe$F$K_9-!2VJKSub2!{sG_q0*phSl3$Zc_}1_NZ_ykiZq*CT8mY12 zM065DN>k3z#WSBjs(p3WZWA_>+Z0&YjPuM5;DtIjekjMf;oy&!hxbk1!xk##({?Xf za>8h7k)%%D7qQKBz%+kdhI?^~g0J(ete{*CGHLUkmnD0_8YeQ&Uq{I;yGi7x zWD{#Ae<@t|DgFuEAI}%@ga8w4sM^bbjM5nfuoVM^wqA%v2$FFN%u9T7i4v9`7L4fI za=bJ*C5Z5Rs4Wd~pC54^?gEq9(AJf0p{G)YA#?o&BN<2E7N=d;<+YRr2D zdvA!kVwSAZ*YFq2B>Gb$|LyQZeUjt{K@uV&WT8~NiEK=C1`K~R@^q=}3ldZl$>6U5 z5dKOqgiqGTgbvw^`nP_@0Hed#RR910 literal 0 HcmV?d00001 diff --git a/assets/images_noconvert/tileset.png b/assets/images_noconvert/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..075f054fd4c9dc80156aa5e16cc69403d6dfe86a GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|j67W&Lo9le z=j?6&Z_g}O@Lo&aE|X1d&nB0KKxVdP#vV3Lwu;LK|7L&BeBA^VVek#)nAM(;ln&I* N;OXk;vd$@?2>=YcB6a`( literal 0 HcmV?d00001 diff --git a/assets/tileset.png b/assets/tileset.png deleted file mode 100644 index 006d48f9bf868f65b38d26991be9c0e4cf38c246..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6T`Z5GB1G~o&cW^*V%jc zyVvj=BpL8q@c-~`Zw87>db&7FNRUuqRMSXkYhn~)U}zU$Y%IF{lOL#v N!PC{xWt~$(699j*8$SR5 diff --git a/package.json b/package.json index 1c04100..3a2c97d 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,17 @@ -{ - "name": "Dawn-GB", - "version": "1.0.0", - "main": "index.js", - "repository": "https://github.com/YourWishes/Dawn-GB.git", - "author": "Dominic Masters ", - "license": "MIT", - "scripts": { - "clean": "node ./scripts/clean", - "build": "npm run clean && node ./scripts/build", - "start": "npm run build && bgb64.exe ./build/Penny.gb" - }, - "dependencies": { - "pngjs": "^6.0.0", - "rimraf": "^3.0.2" - } -} +{ + "name": "Dawn-GB", + "version": "1.0.0", + "main": "index.js", + "repository": "https://github.com/YourWishes/Dawn-GB.git", + "author": "Dominic Masters ", + "license": "MIT", + "scripts": { + "clean": "node ./scripts/clean", + "build": "npm run clean && node ./scripts/build", + "start": "npm run build && wine ./bgb/bgb64.exe ./build/Penny.gb" + }, + "dependencies": { + "pngjs": "^6.0.0", + "rimraf": "^3.0.2" + } +} diff --git a/scripts/351.js b/scripts/351.js index 1f5f84a..e29b272 100644 --- a/scripts/351.js +++ b/scripts/351.js @@ -1,5 +1,5 @@ -const process = require('process'); -const { spawnSync, execSync } = require('child_process'); - -execSync(`scp ./build/Penny.gb root@ywbud3:/storage/roms/gb/Penny.gb`); +const process = require('process'); +const { spawnSync, execSync } = require('child_process'); + +execSync(`scp ./build/Penny.gb root@ywbud3:/storage/roms/gb/Penny.gb`); execSync(`echo "systemctl stop emustation.service; killall emulationstation; retroarch -L /lib/libretro/gambatte_libretro.so '/storage/roms/gb/Penny.gb';" | ssh root@ywbud3 /bin/bash`); \ No newline at end of file diff --git a/scripts/build.js b/scripts/build.js index 4a066fd..5a6e824 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,124 +1,124 @@ -const fs = require('fs'); -const path = require('path'); -const process = require('process'); -const { spawnSync, execSync } = require('child_process'); -const { png2gb } = require('./png2gb'); -const { string2gb } = require('./string2gb'); - -const DIR_BUILD = path.resolve('build'); -const DIR_GENERATED = path.resolve(DIR_BUILD, 'generated'); -const DIR_OBJ = path.resolve(DIR_BUILD, 'obj'); -const DIR_SRC = path.resolve('src'); -const DIR_GBDK = path.resolve(process.env['GBDKDIR']); -const DIR_ASSETS = path.resolve('assets'); -const DIR_IMAGES = path.resolve(DIR_ASSETS, 'images'); - -const FILE_OUT = path.resolve(DIR_BUILD, 'Penny.gb'); -const FILE_LINKFILE = path.join(DIR_BUILD, `linkflile.lk`); - -const LCC = path.join(DIR_GBDK, 'bin', 'lcc'); -const LCCFLAGS = `-I${DIR_GENERATED} -I${DIR_SRC}`; - -const compiledSources = []; - -// Create build dirs -[ - DIR_BUILD, DIR_GENERATED, DIR_OBJ -].forEach(d => { - if(fs.existsSync(d)) return; - fs.mkdirSync(d); -}); - -// Scandir -const buildSourceFiles = directory => { - const sources = []; - - fs.readdirSync(directory).forEach(file => { - const fullPath = path.join(directory, file); - const stats = fs.statSync(fullPath); - if(stats.isDirectory()) { - sources.push(...buildSourceFiles(fullPath)); - return; - } - - if(file.endsWith('.c')) sources.push(fullPath); - }); - - return sources; -} - -const logOut = (out, buffer) => { - const str = [ - out.stderr, out.stdout - ].filter(n => n) - .map(n => n.toString()) - .filter(n => n) - .join('') - ; - if(!str || !str.length) return; - buffer(str); -} - -const compileC = (cFile) => { - const fileNameOut = path.basename(cFile, '.c') + '.o'; - const fileOut = path.join(DIR_OBJ, fileNameOut); - - compiledSources.push(path.join(fileNameOut)); - - if(fs.existsSync(fileOut)) return; - - let result; - try { - result = execSync(`${LCC} ${LCCFLAGS} -c -o ${fileOut} ${cFile}`); - logOut(result, console.log); - } catch(e) { - logOut(e, e => { - console.error(e); - process.exit(1); - }); - throw e; - } -} - -// Generate strings -// let dataStringH = '#pragma once\n#include "libs.h"\n'; -// let dataStringC = '#include "STRINGS.h"\n'; -// Object.entries(GAME_STRINGS).forEach(entry => { -// const [ name, str ] = entry; -// const { dataH, dataC } = string2gb(str, name); -// dataStringH += dataH+'\n', dataStringC += dataC+'\n'; -// }); -// fs.writeFileSync(path.join(DIR_GENERATED, 'STRINGS.h'), dataStringH); -// fs.writeFileSync(path.join(DIR_GENERATED, 'STRINGS.c'), dataStringC); -// compileC(path.join(DIR_GENERATED, 'STRINGS.c')); - -// Gen imagery -fs.readdirSync(DIR_IMAGES).forEach(img => { - if(!img.endsWith(".png")) return; - const { fileC, fileH } = png2gb( - path.join(DIR_IMAGES, img), DIR_GENERATED, - path.basename(img, '.png').toUpperCase() - ); - compileC(fileC); -}) - -// Get a list of sources and build each of them prior to linking. -const allSources = buildSourceFiles(DIR_SRC); -for(let i = 0; i < allSources.length; i++) { - compileC(allSources[i]); -} - -// Generate a linkfile. -fs.writeFileSync(FILE_LINKFILE, compiledSources.map(cs=>{ - return path.join(DIR_OBJ, cs); -}).join('\n')); - -// Compile BIN -let result; -try { - result = execSync(`${LCC} ${LCCFLAGS} -o ${FILE_OUT} -Wl-f${FILE_LINKFILE}`); - logOut(result, console.log); -} catch(e) { - logOut(e, console.error); - process.exit(1); +const fs = require('fs'); +const path = require('path'); +const process = require('process'); +const { spawnSync, execSync } = require('child_process'); +const { png2gb } = require('./png2gb'); +const { string2gb } = require('./string2gb'); + +const DIR_BUILD = path.resolve('build'); +const DIR_GENERATED = path.resolve(DIR_BUILD, 'generated'); +const DIR_OBJ = path.resolve(DIR_BUILD, 'obj'); +const DIR_SRC = path.resolve('src'); +const DIR_GBDK = path.resolve(process.env['GBDKDIR']); +const DIR_ASSETS = path.resolve('assets'); +const DIR_IMAGES = path.resolve(DIR_ASSETS, 'images'); + +const FILE_OUT = path.resolve(DIR_BUILD, 'Penny.gb'); +const FILE_LINKFILE = path.join(DIR_BUILD, `linkflile.lk`); + +const LCC = path.join(DIR_GBDK, 'bin', 'lcc'); +const LCCFLAGS = `-I${DIR_GENERATED} -I${DIR_SRC}`; + +const compiledSources = []; + +// Create build dirs +[ + DIR_BUILD, DIR_GENERATED, DIR_OBJ +].forEach(d => { + if(fs.existsSync(d)) return; + fs.mkdirSync(d); +}); + +// Scandir +const buildSourceFiles = directory => { + const sources = []; + + fs.readdirSync(directory).forEach(file => { + const fullPath = path.join(directory, file); + const stats = fs.statSync(fullPath); + if(stats.isDirectory()) { + sources.push(...buildSourceFiles(fullPath)); + return; + } + + if(file.endsWith('.c')) sources.push(fullPath); + }); + + return sources; +} + +const logOut = (out, buffer) => { + const str = [ + out.stderr, out.stdout + ].filter(n => n) + .map(n => n.toString()) + .filter(n => n) + .join('') + ; + if(!str || !str.length) return; + buffer(str); +} + +const compileC = (cFile) => { + const fileNameOut = path.basename(cFile, '.c') + '.o'; + const fileOut = path.join(DIR_OBJ, fileNameOut); + + compiledSources.push(path.join(fileNameOut)); + + if(fs.existsSync(fileOut)) return; + + let result; + try { + result = execSync(`${LCC} ${LCCFLAGS} -c -o ${fileOut} ${cFile}`); + logOut(result, console.log); + } catch(e) { + logOut(e, e => { + console.error(e); + process.exit(1); + }); + throw e; + } +} + +// Generate strings +// let dataStringH = '#pragma once\n#include "libs.h"\n'; +// let dataStringC = '#include "STRINGS.h"\n'; +// Object.entries(GAME_STRINGS).forEach(entry => { +// const [ name, str ] = entry; +// const { dataH, dataC } = string2gb(str, name); +// dataStringH += dataH+'\n', dataStringC += dataC+'\n'; +// }); +// fs.writeFileSync(path.join(DIR_GENERATED, 'STRINGS.h'), dataStringH); +// fs.writeFileSync(path.join(DIR_GENERATED, 'STRINGS.c'), dataStringC); +// compileC(path.join(DIR_GENERATED, 'STRINGS.c')); + +// Gen imagery +fs.readdirSync(DIR_IMAGES).forEach(img => { + if(!img.endsWith(".png")) return; + const { fileC, fileH } = png2gb( + path.join(DIR_IMAGES, img), DIR_GENERATED, + path.basename(img, '.png').toUpperCase() + ); + compileC(fileC); +}) + +// Get a list of sources and build each of them prior to linking. +const allSources = buildSourceFiles(DIR_SRC); +for(let i = 0; i < allSources.length; i++) { + compileC(allSources[i]); +} + +// Generate a linkfile. +fs.writeFileSync(FILE_LINKFILE, compiledSources.map(cs=>{ + return path.join(DIR_OBJ, cs); +}).join('\n')); + +// Compile BIN +let result; +try { + result = execSync(`${LCC} ${LCCFLAGS} -o ${FILE_OUT} -Wl-f${FILE_LINKFILE}`); + logOut(result, console.log); +} catch(e) { + logOut(e, console.error); + process.exit(1); } \ No newline at end of file diff --git a/scripts/clean.js b/scripts/clean.js index 9de57db..92c9abd 100644 --- a/scripts/clean.js +++ b/scripts/clean.js @@ -1,4 +1,4 @@ -const rimraf = require('rimraf'); - -console.log('🔥 Cleaning'); +const rimraf = require('rimraf'); + +console.log('🔥 Cleaning'); rimraf.sync('build'); \ No newline at end of file diff --git a/scripts/common.js b/scripts/common.js index 3b2b200..994ea27 100644 --- a/scripts/common.js +++ b/scripts/common.js @@ -1,7 +1,7 @@ -const TILE_WIDTH = 8; -const TILE_HEIGHT = 8; - -module.exports = { - TILE_WIDTH, - TILE_HEIGHT +const TILE_WIDTH = 8; +const TILE_HEIGHT = 8; + +module.exports = { + TILE_WIDTH, + TILE_HEIGHT } \ No newline at end of file diff --git a/scripts/gb2png.js b/scripts/gb2png.js index 160ab41..c391ae0 100644 --- a/scripts/gb2png.js +++ b/scripts/gb2png.js @@ -1,88 +1,88 @@ -const PNG = require('pngjs').PNG; -const fs = require('fs'); -const { - TILE_WIDTH, - TILE_HEIGHT -} = require('./common'); - -const colorPixel = (id) => { - if(id === undefined) id = 3; - if(id === 3) return { r: 8, g: 24, b: 32 }; - if(id === 2) return { r: 52, g: 104, b: 86 }; - if(id === 1) return { r: 136, g: 192, b: 112 }; - if(id === 0) return { r: 224, g: 248, b: 208 }; - throw new Error(); -} - -const gb2png = (DATA, fileOut) => { - // Begin - const PIXELS = DATA.length / 2 * TILE_WIDTH; - const DATA_WIDTH = TILE_WIDTH; - const DATA_HEIGHT = PIXELS / DATA_WIDTH; - - // Create output image - const imageData = new PNG({ - width: DATA_WIDTH, - height: DATA_HEIGHT - }); - - // Convert data into pixels - const pixelsOut = []; - for(let i = 0; i < DATA.length; i += 2) { - const low = DATA[i]; - const high = DATA[i+1]; - - for(let j = 0; j < 8; j++) { - const mask = 0x80 >> j; - const pixel = (low & mask ? 1 : 0) + (high & mask ? 2 : 0); - pixelsOut.push(pixel); - } - } - - // Buffer data output - for(let y = 0; y < DATA_HEIGHT; y++) { - for(let x = 0; x < DATA_WIDTH; x++) { - const id = (DATA_WIDTH * y + x); - const color = colorPixel(pixelsOut[id]); - const idx = id << 2; - - imageData.data[idx] = color.r; - imageData.data[idx+1] = color.g; - imageData.data[idx+2] = color.b; - imageData.data[idx+3] = 0xFF; - } - } - const buffer = PNG.sync.write(imageData, { }); - fs.writeFileSync(fileOut, buffer); -} - -// // Now work out tile data -// if(TILEMAP.length) { -// for(let i = 0; i < TILEMAP.length; i++) { -// const tileX = i % TILEMAP_WIDTH; -// const tileY = Math.floor(i / TILEMAP_WIDTH); -// const tile = TILEMAP[i]; - -// for(let j = 0; j < TILE_WIDTH*TILE_HEIGHT; j++) { -// const outI = ( -// (tileX * TILE_WIDTH) + (tileY * TILE_HEIGHT * TILEMAP_PIXEL_WIDTH) + -// ((j % TILE_WIDTH) + (Math.floor(j / TILE_WIDTH) * TILEMAP_PIXEL_WIDTH)) -// ); -// const idx = outI << 2; -// const pixelI = (tile * TILE_WIDTH * TILE_HEIGHT) + j; -// const color = colorPixel(pixelsOut[pixelI]); - -// tileData.data[idx] = color.r; -// tileData.data[idx+1] = color.g; -// tileData.data[idx+2] = color.b; -// tileData.data[idx+3] = 0xFF; -// } -// } - -// const buffer2 = PNG.sync.write(tileData, { }); -// fs.writeFileSync('out.png', buffer2); -// } - -module.exports = { - gb2png +const PNG = require('pngjs').PNG; +const fs = require('fs'); +const { + TILE_WIDTH, + TILE_HEIGHT +} = require('./common'); + +const colorPixel = (id) => { + if(id === undefined) id = 3; + if(id === 3) return { r: 8, g: 24, b: 32 }; + if(id === 2) return { r: 52, g: 104, b: 86 }; + if(id === 1) return { r: 136, g: 192, b: 112 }; + if(id === 0) return { r: 224, g: 248, b: 208 }; + throw new Error(); +} + +const gb2png = (DATA, fileOut) => { + // Begin + const PIXELS = DATA.length / 2 * TILE_WIDTH; + const DATA_WIDTH = TILE_WIDTH; + const DATA_HEIGHT = PIXELS / DATA_WIDTH; + + // Create output image + const imageData = new PNG({ + width: DATA_WIDTH, + height: DATA_HEIGHT + }); + + // Convert data into pixels + const pixelsOut = []; + for(let i = 0; i < DATA.length; i += 2) { + const low = DATA[i]; + const high = DATA[i+1]; + + for(let j = 0; j < 8; j++) { + const mask = 0x80 >> j; + const pixel = (low & mask ? 1 : 0) + (high & mask ? 2 : 0); + pixelsOut.push(pixel); + } + } + + // Buffer data output + for(let y = 0; y < DATA_HEIGHT; y++) { + for(let x = 0; x < DATA_WIDTH; x++) { + const id = (DATA_WIDTH * y + x); + const color = colorPixel(pixelsOut[id]); + const idx = id << 2; + + imageData.data[idx] = color.r; + imageData.data[idx+1] = color.g; + imageData.data[idx+2] = color.b; + imageData.data[idx+3] = 0xFF; + } + } + const buffer = PNG.sync.write(imageData, { }); + fs.writeFileSync(fileOut, buffer); +} + +// // Now work out tile data +// if(TILEMAP.length) { +// for(let i = 0; i < TILEMAP.length; i++) { +// const tileX = i % TILEMAP_WIDTH; +// const tileY = Math.floor(i / TILEMAP_WIDTH); +// const tile = TILEMAP[i]; + +// for(let j = 0; j < TILE_WIDTH*TILE_HEIGHT; j++) { +// const outI = ( +// (tileX * TILE_WIDTH) + (tileY * TILE_HEIGHT * TILEMAP_PIXEL_WIDTH) + +// ((j % TILE_WIDTH) + (Math.floor(j / TILE_WIDTH) * TILEMAP_PIXEL_WIDTH)) +// ); +// const idx = outI << 2; +// const pixelI = (tile * TILE_WIDTH * TILE_HEIGHT) + j; +// const color = colorPixel(pixelsOut[pixelI]); + +// tileData.data[idx] = color.r; +// tileData.data[idx+1] = color.g; +// tileData.data[idx+2] = color.b; +// tileData.data[idx+3] = 0xFF; +// } +// } + +// const buffer2 = PNG.sync.write(tileData, { }); +// fs.writeFileSync('out.png', buffer2); +// } + +module.exports = { + gb2png }; \ No newline at end of file diff --git a/scripts/gbc_old.js b/scripts/gbc_old.js index 5b4a16a..36b7de1 100644 --- a/scripts/gbc_old.js +++ b/scripts/gbc_old.js @@ -1,247 +1,247 @@ -const fs = require('fs'); -const { PNG } = require('pngjs'); -const path = require('path'); - -const TRANSPARENT = { r: 0, g: 0, b: 0, a: 0 }; -const WHITE = { r: 255, g: 255, b : 255, a: 255 }; -const RED = { r: 255, g: 0, b: 0, a: 255 }; - -const TILE_WIDTH = 8; -const TILE_HEIGHT = 8; - -// Helpers -const pixelIsSame = (left, right, alpha) => { - if(left.r !== right.r) return false; - if(left.g !== right.g) return false; - if(left.b !== right.b) return false; - if(!alpha) return true; - return left.a === right.a; -} - -const imageOut = (pixels, width, fileName) => { - const png = new PNG({ - width, - height: pixels.length / width - }); - - pixels.forEach((pixel, i) => { - const x = i % width; - const y = (i - x) / width; - const idx = (width * y + x) << 2; - png.data[idx] = pixel.r; - png.data[idx+1] = pixel.g; - png.data[idx+2] = pixel.b; - png.data[idx+3] = pixel.a; - }); - - if(!fs.existsSync('out')) fs.mkdirSync('out'); - - const out = PNG.sync.write(png); - fs.writeFileSync(path.join('out', fileName), out); -} - -const imageIn = fileName => { - const data = fs.readFileSync(fileName); - const png = PNG.sync.read(data); - const pixels = []; - - for(let y = 0; y < png.height; y++) { - for (let x = 0; x < png.width; x++) { - let idx = (png.width * y + x) << 2; - const r = png.data[idx]; - const g = png.data[idx+1]; - const b = png.data[idx+2]; - const a = png.data[idx+3]; - - let pixel = { r, g, b, a }; - if(a === 0) { - pixel = { ...WHITE }; - } else { - pixel.a = 255; - } - - pixels.push(pixel); - } - } - - return { pixels, width: png.width, height: png.height }; -} - -const tileFromPixel = (x, y, original) => { - const byEightX = Math.floor(x / 8); - const byEightY = Math.floor(y / 8); - const byEightWidth = Math.floor(original.width / 8); - const byEightId = byEightX + (byEightY * byEightWidth); - - return { x: byEightX, y: byEightY, columns: byEightWidth, id: byEightId }; -} - -// Read Input File -const original = imageIn('bruh.png'); - -const columns = (original.width / TILE_WIDTH); -const rows = (original.height / TILE_HEIGHT); - -// Foreach pixel -const palette = []; -const paletteByEight = []; -const withPaletteOverflows = []; - -for(let y = 0; y < original.height; y++) { - for(let x = 0; x < original.width; x++) { - const id = x + (y * original.width); - const pixel = original.pixels[id]; - let errorPixel = { ...pixel }; - const tile = tileFromPixel(x, y, original); - - // Handle palettes - if(pixel.a != 0) { - const pb8 = (paletteByEight[tile.id] = paletteByEight[tile.id] || []); - - if(!pb8.some(p => pixelIsSame(pixel, p))) { - pb8.push(pixel); - } - // Handle palette overflow - if(pb8.length > 4) errorPixel = { ...RED }; - - // Append to palette - if(!palette.some(p => pixelIsSame(pixel, p))) palette.push(pixel); - } - - withPaletteOverflows.push(errorPixel); - } -} - -// Generate the palette set image -const outPaletteByEight = []; -let outByEightWidth = 1; -paletteByEight.forEach((pal,y) => { - pal.forEach((p,x) => { - outByEightWidth = Math.max(outByEightWidth, x+1); - }) -}); -paletteByEight.forEach((pal,y) => { - for(let x = 0; x < outByEightWidth; x++) { - outPaletteByEight.push(x >= pal.length ? TRANSPARENT : pal[x]); - } -}); - -// Now determine for each TILE what palette to use. -const paletteGroups = []; -const gbVersion = []; -const paletteImage = []; - -for(let y = 0; y < original.height; y++) { - for(let x = 0; x < original.width; x++) { - const id = x + (y * original.width); - const pixel = original.pixels[id]; - const tile = tileFromPixel(x, y, original); - - // Get the palette - const paletteSet = paletteByEight[tile.id]; - - // Check for matching - let palId = paletteGroups.findIndex(pg => { - // Check for cases where one of the pallet group palettes may have - // less pixels than the current set we're checking, e.g. we do a tile that - // has only two colors, then we iterate over a tile with 4 colors that has - // two colors shared with that other tile. In that case we just add our - // two extra colors. - if(paletteSet.length > pg.length) { - return pg.every(p => paletteSet.some(pss => pixelIsSame(pss, p))); - } else { - return paletteSet.every(p => pg.some(pgs => pixelIsSame(pgs, p))); - } - }); - - if(palId === -1) { - palId = paletteGroups.length; - paletteGroups.push(paletteSet); - } - - const paletteGroupSet = paletteGroups[palId]; - - // This is where we correct the missing pixels if we share that tileset from - // earlier - paletteSet.forEach(ps => { - const existing = paletteGroupSet.some(pgs => pixelIsSame(pgs, ps)); - if(existing) return; - paletteGroupSet.push(ps); - }); - - // Sort the paletteGroupSet... - const pgsGetWeight = thing => { - return thing.r + thing.g + thing.b; - } - paletteGroupSet.sort((l,r) => { - return pgsGetWeight(l) - pgsGetWeight(r); - }); - - const examples = [ - /* 0 */{ r: 0, g: 0, b: 0, a: 255 }, - /* 1 */{ r: 255, g: 0, b: 0, a: 255 }, - /* 2 */{ r: 0, g: 255, b: 0, a: 255 }, - /* 3 */{ r: 0, g: 0, b: 255, a: 255 }, - /* 4 */{ r: 255, g: 255, b: 0, a: 255 }, - /* 5 */{ r: 255, g: 0, b: 255, a: 255 }, - /* 6 */{ r: 0, g: 255, b: 255, a: 255 }, - /* 7 */{ r: 255, g: 255, b: 255, a: 255 }, - - /* S */{ r: 100, g: 0, b: 0, a: 255 }, - /* S */{ r: 0, g: 100, b: 0, a: 255 }, - /* S */{ r: 0, g: 0, b: 100, a: 255 }, - /* S */{ r: 100, g: 100, b: 0, a: 255 }, - /* S */{ r: 0, g: 100, b: 100, a: 255 }, - /* S */{ r: 100, g: 0, b: 100, a: 255 }, - /* S */{ r: 100, g: 100, b: 100, a: 255 }, - ]; - paletteImage.push(examples[palId]); - - const pixelIndex = paletteGroupSet.findIndex(ps => pixelIsSame(ps, pixel)); - const nonColor = [ - { r: 8, g: 24, b: 32, a: 255 }, - { r: 52, g: 104, b: 86, a: 255 }, - { r: 136, g: 192, b: 112, a: 255 }, - { r: 224, g: 248, b: 208, a: 255 } - ]; - gbVersion.push(nonColor[pixelIndex % nonColor.length]); - } -} -console.log('Found', paletteGroups.length, 'palettes'); - -imageOut(original.pixels, original.width, 'original.png'); -imageOut(withPaletteOverflows, original.width, 'errors.png'); -imageOut(palette, palette.length, 'palette.png'); -imageOut(outPaletteByEight, outByEightWidth, 'paletteByEight.png'); -imageOut(paletteImage, original.width, 'palettes.png'); -imageOut(gbVersion, original.width, 'gameboy.png'); - -// Now generate the GB files -// let rearranged = []; -// let n = 0; -// for(let i = 0; i < columns * rows; i++) { -// const tileX = i % columns; -// const tileY = Math.floor(i / columns) % rows; - -// for(let y = 0; y < TILE_HEIGHT; y++) { -// for(let x = 0; x < TILE_WIDTH; x++) { -// const px = (tileX * TILE_WIDTH) + x; -// const py = (tileY * TILE_HEIGHT) + y; -// const pi = (py * png.width) + px; -// rearranged[n++] = original.pixels[pi]; -// } -// } -// } - -// // Now turn into a tileset -// const bits = []; -// for(let i = 0; i < rearranged.length; i += TILE_WIDTH) { -// let lowBits = 0x00; -// let highBits = 0x00; -// for(let j = 0; j < TILE_WIDTH; j++) { -// const pixel = rearranged[i + j]; -// lowBits = lowBits | ((pixel & 0x01) << (7-j)); -// highBits = highBits | ((pixel & 0x02) >> 1 << (7-j)); -// } -// bits.push(lowBits, highBits); +const fs = require('fs'); +const { PNG } = require('pngjs'); +const path = require('path'); + +const TRANSPARENT = { r: 0, g: 0, b: 0, a: 0 }; +const WHITE = { r: 255, g: 255, b : 255, a: 255 }; +const RED = { r: 255, g: 0, b: 0, a: 255 }; + +const TILE_WIDTH = 8; +const TILE_HEIGHT = 8; + +// Helpers +const pixelIsSame = (left, right, alpha) => { + if(left.r !== right.r) return false; + if(left.g !== right.g) return false; + if(left.b !== right.b) return false; + if(!alpha) return true; + return left.a === right.a; +} + +const imageOut = (pixels, width, fileName) => { + const png = new PNG({ + width, + height: pixels.length / width + }); + + pixels.forEach((pixel, i) => { + const x = i % width; + const y = (i - x) / width; + const idx = (width * y + x) << 2; + png.data[idx] = pixel.r; + png.data[idx+1] = pixel.g; + png.data[idx+2] = pixel.b; + png.data[idx+3] = pixel.a; + }); + + if(!fs.existsSync('out')) fs.mkdirSync('out'); + + const out = PNG.sync.write(png); + fs.writeFileSync(path.join('out', fileName), out); +} + +const imageIn = fileName => { + const data = fs.readFileSync(fileName); + const png = PNG.sync.read(data); + const pixels = []; + + for(let y = 0; y < png.height; y++) { + for (let x = 0; x < png.width; x++) { + let idx = (png.width * y + x) << 2; + const r = png.data[idx]; + const g = png.data[idx+1]; + const b = png.data[idx+2]; + const a = png.data[idx+3]; + + let pixel = { r, g, b, a }; + if(a === 0) { + pixel = { ...WHITE }; + } else { + pixel.a = 255; + } + + pixels.push(pixel); + } + } + + return { pixels, width: png.width, height: png.height }; +} + +const tileFromPixel = (x, y, original) => { + const byEightX = Math.floor(x / 8); + const byEightY = Math.floor(y / 8); + const byEightWidth = Math.floor(original.width / 8); + const byEightId = byEightX + (byEightY * byEightWidth); + + return { x: byEightX, y: byEightY, columns: byEightWidth, id: byEightId }; +} + +// Read Input File +const original = imageIn('bruh.png'); + +const columns = (original.width / TILE_WIDTH); +const rows = (original.height / TILE_HEIGHT); + +// Foreach pixel +const palette = []; +const paletteByEight = []; +const withPaletteOverflows = []; + +for(let y = 0; y < original.height; y++) { + for(let x = 0; x < original.width; x++) { + const id = x + (y * original.width); + const pixel = original.pixels[id]; + let errorPixel = { ...pixel }; + const tile = tileFromPixel(x, y, original); + + // Handle palettes + if(pixel.a != 0) { + const pb8 = (paletteByEight[tile.id] = paletteByEight[tile.id] || []); + + if(!pb8.some(p => pixelIsSame(pixel, p))) { + pb8.push(pixel); + } + // Handle palette overflow + if(pb8.length > 4) errorPixel = { ...RED }; + + // Append to palette + if(!palette.some(p => pixelIsSame(pixel, p))) palette.push(pixel); + } + + withPaletteOverflows.push(errorPixel); + } +} + +// Generate the palette set image +const outPaletteByEight = []; +let outByEightWidth = 1; +paletteByEight.forEach((pal,y) => { + pal.forEach((p,x) => { + outByEightWidth = Math.max(outByEightWidth, x+1); + }) +}); +paletteByEight.forEach((pal,y) => { + for(let x = 0; x < outByEightWidth; x++) { + outPaletteByEight.push(x >= pal.length ? TRANSPARENT : pal[x]); + } +}); + +// Now determine for each TILE what palette to use. +const paletteGroups = []; +const gbVersion = []; +const paletteImage = []; + +for(let y = 0; y < original.height; y++) { + for(let x = 0; x < original.width; x++) { + const id = x + (y * original.width); + const pixel = original.pixels[id]; + const tile = tileFromPixel(x, y, original); + + // Get the palette + const paletteSet = paletteByEight[tile.id]; + + // Check for matching + let palId = paletteGroups.findIndex(pg => { + // Check for cases where one of the pallet group palettes may have + // less pixels than the current set we're checking, e.g. we do a tile that + // has only two colors, then we iterate over a tile with 4 colors that has + // two colors shared with that other tile. In that case we just add our + // two extra colors. + if(paletteSet.length > pg.length) { + return pg.every(p => paletteSet.some(pss => pixelIsSame(pss, p))); + } else { + return paletteSet.every(p => pg.some(pgs => pixelIsSame(pgs, p))); + } + }); + + if(palId === -1) { + palId = paletteGroups.length; + paletteGroups.push(paletteSet); + } + + const paletteGroupSet = paletteGroups[palId]; + + // This is where we correct the missing pixels if we share that tileset from + // earlier + paletteSet.forEach(ps => { + const existing = paletteGroupSet.some(pgs => pixelIsSame(pgs, ps)); + if(existing) return; + paletteGroupSet.push(ps); + }); + + // Sort the paletteGroupSet... + const pgsGetWeight = thing => { + return thing.r + thing.g + thing.b; + } + paletteGroupSet.sort((l,r) => { + return pgsGetWeight(l) - pgsGetWeight(r); + }); + + const examples = [ + /* 0 */{ r: 0, g: 0, b: 0, a: 255 }, + /* 1 */{ r: 255, g: 0, b: 0, a: 255 }, + /* 2 */{ r: 0, g: 255, b: 0, a: 255 }, + /* 3 */{ r: 0, g: 0, b: 255, a: 255 }, + /* 4 */{ r: 255, g: 255, b: 0, a: 255 }, + /* 5 */{ r: 255, g: 0, b: 255, a: 255 }, + /* 6 */{ r: 0, g: 255, b: 255, a: 255 }, + /* 7 */{ r: 255, g: 255, b: 255, a: 255 }, + + /* S */{ r: 100, g: 0, b: 0, a: 255 }, + /* S */{ r: 0, g: 100, b: 0, a: 255 }, + /* S */{ r: 0, g: 0, b: 100, a: 255 }, + /* S */{ r: 100, g: 100, b: 0, a: 255 }, + /* S */{ r: 0, g: 100, b: 100, a: 255 }, + /* S */{ r: 100, g: 0, b: 100, a: 255 }, + /* S */{ r: 100, g: 100, b: 100, a: 255 }, + ]; + paletteImage.push(examples[palId]); + + const pixelIndex = paletteGroupSet.findIndex(ps => pixelIsSame(ps, pixel)); + const nonColor = [ + { r: 8, g: 24, b: 32, a: 255 }, + { r: 52, g: 104, b: 86, a: 255 }, + { r: 136, g: 192, b: 112, a: 255 }, + { r: 224, g: 248, b: 208, a: 255 } + ]; + gbVersion.push(nonColor[pixelIndex % nonColor.length]); + } +} +console.log('Found', paletteGroups.length, 'palettes'); + +imageOut(original.pixels, original.width, 'original.png'); +imageOut(withPaletteOverflows, original.width, 'errors.png'); +imageOut(palette, palette.length, 'palette.png'); +imageOut(outPaletteByEight, outByEightWidth, 'paletteByEight.png'); +imageOut(paletteImage, original.width, 'palettes.png'); +imageOut(gbVersion, original.width, 'gameboy.png'); + +// Now generate the GB files +// let rearranged = []; +// let n = 0; +// for(let i = 0; i < columns * rows; i++) { +// const tileX = i % columns; +// const tileY = Math.floor(i / columns) % rows; + +// for(let y = 0; y < TILE_HEIGHT; y++) { +// for(let x = 0; x < TILE_WIDTH; x++) { +// const px = (tileX * TILE_WIDTH) + x; +// const py = (tileY * TILE_HEIGHT) + y; +// const pi = (py * png.width) + px; +// rearranged[n++] = original.pixels[pi]; +// } +// } +// } + +// // Now turn into a tileset +// const bits = []; +// for(let i = 0; i < rearranged.length; i += TILE_WIDTH) { +// let lowBits = 0x00; +// let highBits = 0x00; +// for(let j = 0; j < TILE_WIDTH; j++) { +// const pixel = rearranged[i + j]; +// lowBits = lowBits | ((pixel & 0x01) << (7-j)); +// highBits = highBits | ((pixel & 0x02) >> 1 << (7-j)); +// } +// bits.push(lowBits, highBits); // } \ No newline at end of file diff --git a/scripts/png2gb.js b/scripts/png2gb.js index 1cd355e..7c6aaa0 100644 --- a/scripts/png2gb.js +++ b/scripts/png2gb.js @@ -1,93 +1,101 @@ -const PNG = require('pngjs').PNG; -const path = require('path'); -const fs = require('fs'); -const { arrayToString } = require('./util'); -const { - TILE_WIDTH, - TILE_HEIGHT -} = require('./common'); - - -const getPixelValue = (pixel) => { - if(pixel.g === 188) return 0; - if(pixel.g === 172) return 1; - if(pixel.g === 98) return 2; - if(pixel.g === 56) return 3; - throw new Error(); -} - -const png2gb = (fileIn, dirOut, name) => { - const data = fs.readFileSync(fileIn); - const png = PNG.sync.read(data); - - // Convert PNG pixels into 0x00-0x03 - const pixels = []; - for(let y = 0; y < png.height; y++) { - for(let x = 0; x < png.width; x++) { - const id = x + (y * png.width); - const idx = id << 2; - const r = png.data[idx]; - const g = png.data[idx+1]; - const b = png.data[idx+2]; - const value = getPixelValue({ r, g, b }); - pixels.push(value); - } - } - - // Now take these raw pixels and extract the tiles themselves - let rearranged = []; - const columns = (png.width / TILE_WIDTH); - const rows = (png.height / TILE_HEIGHT); - let n = 0; - for(let i = 0; i < columns * rows; i++) { - const tileX = i % columns; - const tileY = Math.floor(i / columns) % rows; - - for(let y = 0; y < TILE_HEIGHT; y++) { - for(let x = 0; x < TILE_WIDTH; x++) { - const px = (tileX * TILE_WIDTH) + x; - const py = (tileY * TILE_HEIGHT) + y; - const pi = (py * png.width) + px; - rearranged[n++] = pixels[pi]; - } - } - } - - // Now turn into a tileset - const bits = []; - for(let i = 0; i < rearranged.length; i += TILE_WIDTH) { - let lowBits = 0x00; - let highBits = 0x00; - for(let j = 0; j < TILE_WIDTH; j++) { - const pixel = rearranged[i + j]; - lowBits = lowBits | ((pixel & 0x01) << (7-j)); - highBits = highBits | ((pixel & 0x02) >> 1 << (7-j)); - } - bits.push(lowBits, highBits); - } - - let outH = ''; - outH += `#include "libs.h"\n\n` - outH += `#define ${name}_IMAGE_WIDTH ${png.width}\n`; - outH += `#define ${name}_IMAGE_HEIGHT ${png.height}\n`; - outH += `#define ${name}_IMAGE_COLUMNS ${png.width / TILE_WIDTH}\n`; - outH += `#define ${name}_IMAGE_ROWS ${png.height / TILE_HEIGHT}\n`; - outH += `#define ${name}_IMAGE_TILES ${columns * rows}\n`; - outH += `extern const uint8_t ${name}_IMAGE[];`; - - let outC = `#include "${name}.h"\n`; - outC += `\nconst uint8_t ${name}_IMAGE[] = {\n${arrayToString(bits)}};`; - - const fileH = path.join(dirOut, name + '.h'); - const fileC = path.join(dirOut, name + '.c'); - - fs.writeFileSync(fileH, outH); - fs.writeFileSync(fileC, outC); - return { fileH, fileC }; -} - -module.exports = { - png2gb -} - +const PNG = require('pngjs').PNG; +const path = require('path'); +const fs = require('fs'); +const { arrayToString } = require('./util'); +const { + TILE_WIDTH, + TILE_HEIGHT +} = require('./common'); + + +const getPixelValue = (pixel) => { + if(pixel.g === 188) return 0; + if(pixel.g === 172) return 1; + if(pixel.g === 98 || pixel.g === 145) return 2; + if(pixel.g === 56) return 3; + if(pixel.a === 0) return 0; + throw new Error(); +} + +const png2gb = (fileIn, dirOut, name) => { + const data = fs.readFileSync(fileIn); + const png = PNG.sync.read(data); + + // Convert PNG pixels into 0x00-0x03 + const pixels = []; + for(let y = 0; y < png.height; y++) { + for(let x = 0; x < png.width; x++) { + const id = x + (y * png.width); + const idx = id << 2; + const r = png.data[idx]; + const g = png.data[idx+1]; + const b = png.data[idx+2]; + const a = png.data[idx+3]; + const pixel = { r, g, b, a }; + try { + const value = getPixelValue(pixel); + pixels.push(value); + } catch(e) { + console.error(`Failed to get color for `, x, y, idx, fileIn, pixel); + throw e; + } + } + } + + // Now take these raw pixels and extract the tiles themselves + let rearranged = []; + const columns = (png.width / TILE_WIDTH); + const rows = (png.height / TILE_HEIGHT); + let n = 0; + for(let i = 0; i < columns * rows; i++) { + const tileX = i % columns; + const tileY = Math.floor(i / columns) % rows; + + for(let y = 0; y < TILE_HEIGHT; y++) { + for(let x = 0; x < TILE_WIDTH; x++) { + const px = (tileX * TILE_WIDTH) + x; + const py = (tileY * TILE_HEIGHT) + y; + const pi = (py * png.width) + px; + rearranged[n++] = pixels[pi]; + } + } + } + + // Now turn into a tileset + const bits = []; + for(let i = 0; i < rearranged.length; i += TILE_WIDTH) { + let lowBits = 0x00; + let highBits = 0x00; + for(let j = 0; j < TILE_WIDTH; j++) { + const pixel = rearranged[i + j]; + lowBits = lowBits | ((pixel & 0x01) << (7-j)); + highBits = highBits | ((pixel & 0x02) >> 1 << (7-j)); + } + bits.push(lowBits, highBits); + } + + let outH = ''; + outH += `#include "libs.h"\n\n` + outH += `#define ${name}_IMAGE_WIDTH ${png.width}\n`; + outH += `#define ${name}_IMAGE_HEIGHT ${png.height}\n`; + outH += `#define ${name}_IMAGE_COLUMNS ${png.width / TILE_WIDTH}\n`; + outH += `#define ${name}_IMAGE_ROWS ${png.height / TILE_HEIGHT}\n`; + outH += `#define ${name}_IMAGE_TILES ${columns * rows}\n`; + outH += `extern const uint8_t ${name}_IMAGE[];`; + + let outC = `#include "${name}.h"\n`; + outC += `\nconst uint8_t ${name}_IMAGE[] = {\n${arrayToString(bits)}};`; + + const fileH = path.join(dirOut, name + '.h'); + const fileC = path.join(dirOut, name + '.c'); + + fs.writeFileSync(fileH, outH); + fs.writeFileSync(fileC, outC); + return { fileH, fileC }; +} + +module.exports = { + png2gb +} + // convert('images/sm.png', 'out.c', 'PENNY'); \ No newline at end of file diff --git a/scripts/string2gb.js b/scripts/string2gb.js index f9255b9..18bec73 100644 --- a/scripts/string2gb.js +++ b/scripts/string2gb.js @@ -1,32 +1,32 @@ -const fs = require('fs'); -const path = require('path'); -const { arrayToString } = require('./util'); - -const FONT_CHARACTER_FIRST = 33; -const FONT_DATA_POSITION = 4; - -const getCodeFrom = l => { - const cc = l.charCodeAt(0) - if(l == '\n' || l == ' ') return cc; - return cc - FONT_CHARACTER_FIRST + FONT_DATA_POSITION -} - -const string2gb = (string, name) => { - const letters = []; - for(let i = 0; i < string.length; i++) { - letters.push(getCodeFrom(string[i])); - } - - let dataH = `#define STR_${name}_LENGTH ${string.length}\n`; - dataH += `extern const uint8_t STR_${name}_DATA[];`; - - let dataC = `const uint8_t STR_${name}_DATA[] = {\n`; - dataC += arrayToString(letters); - dataC += `\n};`; - - return { dataH, dataC }; -} - -module.exports = { - string2gb +const fs = require('fs'); +const path = require('path'); +const { arrayToString } = require('./util'); + +const FONT_CHARACTER_FIRST = 33; +const FONT_DATA_POSITION = 4; + +const getCodeFrom = l => { + const cc = l.charCodeAt(0) + if(l == '\n' || l == ' ') return cc; + return cc - FONT_CHARACTER_FIRST + FONT_DATA_POSITION +} + +const string2gb = (string, name) => { + const letters = []; + for(let i = 0; i < string.length; i++) { + letters.push(getCodeFrom(string[i])); + } + + let dataH = `#define STR_${name}_LENGTH ${string.length}\n`; + dataH += `extern const uint8_t STR_${name}_DATA[];`; + + let dataC = `const uint8_t STR_${name}_DATA[] = {\n`; + dataC += arrayToString(letters); + dataC += `\n};`; + + return { dataH, dataC }; +} + +module.exports = { + string2gb }; \ No newline at end of file diff --git a/scripts/util.js b/scripts/util.js index 8cda31a..16cbd7f 100644 --- a/scripts/util.js +++ b/scripts/util.js @@ -1,21 +1,21 @@ -const arrayToString = arr => { - const b = arr.map(n => { - return '0x' + (n.toString(16).padStart(2, '0').toUpperCase()); - }); - - let str = ''; - for(let i = 0; i < b.length; i += 16) { - str += ' '; - for(let x = i; x < Math.min(i+16, b.length); x++) { - str += b[x]; - str += ','; - } - str += '\n'; - } - - return str; -} - -module.exports = { - arrayToString +const arrayToString = arr => { + const b = arr.map(n => { + return '0x' + (n.toString(16).padStart(2, '0').toUpperCase()); + }); + + let str = ''; + for(let i = 0; i < b.length; i += 16) { + str += ' '; + for(let x = i; x < Math.min(i+16, b.length); x++) { + str += b[x]; + str += ','; + } + str += '\n'; + } + + return str; +} + +module.exports = { + arrayToString } \ No newline at end of file diff --git a/src/conversation/fade.c b/src/conversation/fade.c index 596385f..de92deb 100644 --- a/src/conversation/fade.c +++ b/src/conversation/fade.c @@ -1,69 +1,69 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "fade.h" - -inline void conversationFadeToBlack() { - TIME_FUTURE = TIME_CURRENT; - TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_FADE_TO_BLACK; -} - -inline void conversationFadeFromBlack() { - TIME_FUTURE = TIME_CURRENT; - TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_FADE_FROM_BLACK; -} - -inline void conversationFadeToWhite() { - TIME_FUTURE = TIME_CURRENT; - TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_FADE_TO_WHITE; -} - -inline void conversationFadeFromWhite() { - TIME_FUTURE = TIME_CURRENT; - TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_FADE_FROM_WHITE; -} - -void conversationFadeUpdate() { - uint16_t diff; - - if( - TIME_FUTURE_TYPE < TIME_FUTURE_TYPE_FADE_TO_BLACK || - TIME_FUTURE_TYPE > TIME_FUTURE_TYPE_FADE_FROM_WHITE - ) return; - - diff = TIME_CURRENT - TIME_FUTURE; - - // Now we work out the steps. Time is measured in steps which are made of - // parts of a second. This code assumes that the screen STARTS at the correct - // shade. - if(diff == FADE_STEP) { - // First step - BGP_REG = ( - TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_BLACK ? COMMON_SHADE_DARK : - TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_WHITE ? COMMON_SHADE_BRIGHT : - TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_FROM_BLACK ? COMMON_SHADE_DARKER : - COMMON_SHADE_BRIGHTER - ); - } else if(diff == FADE_STEP * 2) { - // Second step - BGP_REG = ( - TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_BLACK ? COMMON_SHADE_DARKER : - TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_WHITE ? COMMON_SHADE_BRIGHTER : - TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_FROM_BLACK ? COMMON_SHADE_DARK : - COMMON_SHADE_BRIGHT - ); - } else if(diff == FADE_STEP * 3) { - // Third step - BGP_REG = ( - TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_BLACK ? COMMON_SHADE_BLACK : - TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_WHITE ? COMMON_SHADE_WHITE : - COMMON_SHADE_NORMAL - ); - TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_NULL; - conversationQueueNext(); - } +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "fade.h" + +inline void conversationFadeToBlack() { + TIME_FUTURE = TIME_CURRENT; + TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_FADE_TO_BLACK; +} + +inline void conversationFadeFromBlack() { + TIME_FUTURE = TIME_CURRENT; + TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_FADE_FROM_BLACK; +} + +inline void conversationFadeToWhite() { + TIME_FUTURE = TIME_CURRENT; + TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_FADE_TO_WHITE; +} + +inline void conversationFadeFromWhite() { + TIME_FUTURE = TIME_CURRENT; + TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_FADE_FROM_WHITE; +} + +void conversationFadeUpdate() { + uint16_t diff; + + if( + TIME_FUTURE_TYPE < TIME_FUTURE_TYPE_FADE_TO_BLACK || + TIME_FUTURE_TYPE > TIME_FUTURE_TYPE_FADE_FROM_WHITE + ) return; + + diff = TIME_CURRENT - TIME_FUTURE; + + // Now we work out the steps. Time is measured in steps which are made of + // parts of a second. This code assumes that the screen STARTS at the correct + // shade. + if(diff == FADE_STEP) { + // First step + BGP_REG = ( + TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_BLACK ? TILESET_SHADE_DARK : + TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_WHITE ? TILESET_SHADE_BRIGHT : + TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_FROM_BLACK ? TILESET_SHADE_DARKER : + TILESET_SHADE_BRIGHTER + ); + } else if(diff == FADE_STEP * 2) { + // Second step + BGP_REG = ( + TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_BLACK ? TILESET_SHADE_DARKER : + TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_WHITE ? TILESET_SHADE_BRIGHTER : + TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_FROM_BLACK ? TILESET_SHADE_DARK : + TILESET_SHADE_BRIGHT + ); + } else if(diff == FADE_STEP * 3) { + // Third step + BGP_REG = ( + TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_BLACK ? TILESET_SHADE_BLACK : + TIME_FUTURE_TYPE == TIME_FUTURE_TYPE_FADE_TO_WHITE ? TILESET_SHADE_WHITE : + TILESET_SHADE_NORMAL + ); + TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_NULL; + conversationQueueNext(); + } } \ No newline at end of file diff --git a/src/conversation/fade.h b/src/conversation/fade.h index 2355b6e..22cdf41 100644 --- a/src/conversation/fade.h +++ b/src/conversation/fade.h @@ -1,21 +1,21 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" -#include "queue.h" -#include "pause.h" -#include "../display/common.h" - -// This is how many frames it takes to change each shade. -#define FADE_STEP 20 - -inline void conversationFadeToBlack(); -inline void conversationFadeFromBlack(); -inline void conversationFadeToWhite(); -inline void conversationFadeFromWhite(); +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "queue.h" +#include "pause.h" +#include "../sprites/spritetileset.h" + +// This is how many frames it takes to change each shade. +#define FADE_STEP 20 + +inline void conversationFadeToBlack(); +inline void conversationFadeFromBlack(); +inline void conversationFadeToWhite(); +inline void conversationFadeFromWhite(); void conversationFadeUpdate(); \ No newline at end of file diff --git a/src/conversation/frame.c b/src/conversation/frame.c deleted file mode 100644 index 479dae6..0000000 --- a/src/conversation/frame.c +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "frame.h" - -inline void frameBuffer( - uint8_t buffer[], - uint8_t bufferWidth, uint8_t bufferHeight, - uint8_t fill -) { - uint8_t i, j, max; - max = bufferWidth * bufferHeight; - - // Corners - buffer[0] = BORDER_TILE_TOP_LEFT; - buffer[bufferWidth-1] = BORDER_TILE_TOP_RIGHT; - buffer[max-1] = BORDER_TILE_BOTTOM_RIGHT; - buffer[max-bufferWidth] = BORDER_TILE_BOTTOM_LEFT; - - // Edges - for(i = 1; i < bufferWidth-1; i++) { - buffer[i] = BORDER_TILE_TOP_CENTER; - buffer[max - 1 - i] = BORDER_TILE_BOTTOM_CENTER; - } - for(i = 1; i < bufferHeight - 1; i++) { - buffer[bufferWidth * i] = BORDER_TILE_CENTER_LEFT; - buffer[bufferWidth * (i+1) - 1] = BORDER_TILE_CENTER_RIGHT; - } - - // Inner - for(j = 1; j < bufferHeight-1; j++) { - for(i = 1; i < bufferWidth-1; i++) { - buffer[i + (j * bufferWidth)] = fill; - } - } -} \ No newline at end of file diff --git a/src/conversation/frame.h b/src/conversation/frame.h deleted file mode 100644 index d1cf955..0000000 --- a/src/conversation/frame.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" -#include "../util.h" -#include "../display/common.h" -#include "../display/tilemap.h" - -#define BORDER_TILE_TOP_LEFT BORDER_DATA_POSITION -#define BORDER_TILE_TOP_CENTER BORDER_TILE_TOP_LEFT + 1 -#define BORDER_TILE_TOP_RIGHT BORDER_TILE_TOP_CENTER + 1 -#define BORDER_TILE_CENTER_LEFT BORDER_TILE_TOP_RIGHT + 1 -#define BORDER_TILE_CENTER BORDER_TILE_CENTER_LEFT + 1 -#define BORDER_TILE_CENTER_RIGHT BORDER_TILE_CENTER + 1 -#define BORDER_TILE_BOTTOM_LEFT BORDER_TILE_CENTER_RIGHT + 1 -#define BORDER_TILE_BOTTOM_CENTER BORDER_TILE_BOTTOM_LEFT + 1 -#define BORDER_TILE_BOTTOM_RIGHT BORDER_TILE_BOTTOM_CENTER + 1 - -inline void frameBuffer( - uint8_t buffer[], - uint8_t bufferWidth, uint8_t bufferHeight, - uint8_t fill -); \ No newline at end of file diff --git a/src/conversation/pause.c b/src/conversation/pause.c index 89b577a..a4469b1 100644 --- a/src/conversation/pause.c +++ b/src/conversation/pause.c @@ -1,22 +1,22 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "pause.h" - -inline void conversationPause(uint16_t duration) { - TIME_FUTURE = TIME_CURRENT + (duration * TIME_PER_SECOND); - TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_PAUSE; -} - -inline void conversationPauseUpdate() { - if(TIME_FUTURE_TYPE != TIME_FUTURE_TYPE_PAUSE || TIME_CURRENT != TIME_FUTURE) { - return; - } - - TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_NULL; - conversationQueueNext(); +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "pause.h" + +inline void conversationPause(uint16_t duration) { + TIME_FUTURE = TIME_CURRENT + (duration * TIME_PER_SECOND); + TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_PAUSE; +} + +inline void conversationPauseUpdate() { + if(TIME_FUTURE_TYPE != TIME_FUTURE_TYPE_PAUSE || TIME_CURRENT != TIME_FUTURE) { + return; + } + + TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_NULL; + conversationQueueNext(); } \ No newline at end of file diff --git a/src/conversation/pause.h b/src/conversation/pause.h index b0f0c9f..8ddfe8c 100644 --- a/src/conversation/pause.h +++ b/src/conversation/pause.h @@ -1,14 +1,14 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" -#include "../time.h" -#include "queue.h" - -inline void conversationPause(uint16_t duration); +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "../time.h" +#include "queue.h" + +inline void conversationPause(uint16_t duration); inline void conversationPauseUpdate(); \ No newline at end of file diff --git a/src/conversation/questionbox.c b/src/conversation/questionbox.c index 34e802b..c5eccc3 100644 --- a/src/conversation/questionbox.c +++ b/src/conversation/questionbox.c @@ -1,87 +1,87 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "questionbox.h" - -uint8_t QUESTION_BOX_OPTION_COUNT; -uint8_t QUESTION_BOX_OPTION_CURRENT; - -void questionBoxSetOptions(char *title, char **options, uint8_t count) { - uint8_t i, j; - char buffer[TEXTBOX_CHARS_MAX + 1]; - char spaces[QUESTION_BOX_QUESTION_SPACES]; - sprintf(buffer, title); - - i = 0; - QUESTION_BOX_OPTION_CURRENT = 0; - QUESTION_BOX_OPTION_COUNT = count; - - for(i = 0; i < QUESTION_BOX_QUESTION_SPACES; i++) { - spaces[i] = ' '; - } - - // For each row... - for(i = 0; i < count; i++) { - if((i & 1) == 0) { - j = strlen(options[i]); - sprintf(buffer, "%s\n %s", buffer, options[i]); - } else { - j = QUESTION_BOX_QUESTION_SPACES - j; - spaces[j + 1] = '\0'; - sprintf(buffer, "%s%s%s", buffer, spaces, options[i]); - spaces[j + 1] = ' '; - } - } - - conversationTextboxSetText(buffer); - - // Modify the textbox state - TEXTBOX_STATE |= TEXTBOX_STATE_IS_QUESTION; - - // Repurpose old array and draw arrow - spaces[0] = QUESTION_BOX_CURSOR - TEXTBOX_FONT_START + FONT_DATA_POSITION; - set_bkg_tiles(0x01, TEXTBOX_WIN_Y + 0x02, 1, 1, spaces); -} - -inline void questionBoxUpdate() { - uint8_t tiles[1]; - uint8_t i; - - if((TEXTBOX_STATE & (TEXTBOX_STATE_VISIBLE|TEXTBOX_STATE_IS_QUESTION)) == 0) return; - - // Detect input - if(INPUT_PRESSED & J_RIGHT) { - QUESTION_BOX_OPTION_CURRENT++; - } else if(INPUT_PRESSED & J_LEFT) { - QUESTION_BOX_OPTION_CURRENT--; - } else if(INPUT_PRESSED & J_UP) { - QUESTION_BOX_OPTION_CURRENT -= 2; - } else if(INPUT_PRESSED & J_DOWN) { - QUESTION_BOX_OPTION_CURRENT += 2; - } else { - return; - } - - // Bound. - QUESTION_BOX_OPTION_CURRENT = QUESTION_BOX_OPTION_CURRENT % QUESTION_BOX_OPTION_COUNT; - - // Decide where to render arrow - for(i = 0; i < QUESTION_BOX_OPTION_COUNT; i++) { - if(i == QUESTION_BOX_OPTION_CURRENT) { - tiles[0] = QUESTION_BOX_CURSOR - TEXTBOX_FONT_START + FONT_DATA_POSITION; - } else { - tiles[0] = ' ' - TEXTBOX_FONT_START + FONT_DATA_POSITION; - } - set_bkg_tiles( - 0x01 + ((i % 0x02) * QUESTION_BOX_QUESTION_SPACES), - TEXTBOX_WIN_Y + 0x02 + (i / 0x02), - 1, 1, - tiles - ); - } - +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "questionbox.h" + +uint8_t QUESTION_BOX_OPTION_COUNT; +uint8_t QUESTION_BOX_OPTION_CURRENT; + +void questionBoxSetOptions(char *title, char **options, uint8_t count) { + uint8_t i, j; + char buffer[TEXTBOX_CHARS_MAX + 1]; + char spaces[QUESTION_BOX_QUESTION_SPACES]; + sprintf(buffer, title); + + i = 0; + QUESTION_BOX_OPTION_CURRENT = 0; + QUESTION_BOX_OPTION_COUNT = count; + + for(i = 0; i < QUESTION_BOX_QUESTION_SPACES; i++) { + spaces[i] = ' '; + } + + // For each row... + for(i = 0; i < count; i++) { + if((i & 1) == 0) { + j = strlen(options[i]); + sprintf(buffer, "%s\n %s", buffer, options[i]); + } else { + j = QUESTION_BOX_QUESTION_SPACES - j; + spaces[j + 1] = '\0'; + sprintf(buffer, "%s%s%s", buffer, spaces, options[i]); + spaces[j + 1] = ' '; + } + } + + conversationTextboxSetText(buffer); + + // Modify the textbox state + TEXTBOX_STATE |= TEXTBOX_STATE_IS_QUESTION; + + // Repurpose old array and draw arrow + spaces[0] = spriteFontTileFromChar(QUESTION_BOX_CURSOR); + set_bkg_tiles(0x01, TEXTBOX_WIN_Y + 0x02, 1, 1, spaces); +} + +inline void questionBoxUpdate() { + uint8_t tiles[1]; + uint8_t i; + + if((TEXTBOX_STATE & (TEXTBOX_STATE_VISIBLE|TEXTBOX_STATE_IS_QUESTION)) == 0) return; + + // Detect input + if(INPUT_PRESSED & J_RIGHT) { + QUESTION_BOX_OPTION_CURRENT++; + } else if(INPUT_PRESSED & J_LEFT) { + QUESTION_BOX_OPTION_CURRENT--; + } else if(INPUT_PRESSED & J_UP) { + QUESTION_BOX_OPTION_CURRENT -= 2; + } else if(INPUT_PRESSED & J_DOWN) { + QUESTION_BOX_OPTION_CURRENT += 2; + } else { + return; + } + + // Bound. + QUESTION_BOX_OPTION_CURRENT = QUESTION_BOX_OPTION_CURRENT % QUESTION_BOX_OPTION_COUNT; + + // Decide where to render arrow + for(i = 0; i < QUESTION_BOX_OPTION_COUNT; i++) { + if(i == QUESTION_BOX_OPTION_CURRENT) { + tiles[0] = spriteFontTileFromChar(QUESTION_BOX_CURSOR); + } else { + tiles[0] = spriteFontTileFromChar(' '); + } + set_bkg_tiles( + 0x01 + ((i % 0x02) * QUESTION_BOX_QUESTION_SPACES), + TEXTBOX_WIN_Y + 0x02 + (i / 0x02), + 1, 1, + tiles + ); + } + } \ No newline at end of file diff --git a/src/conversation/questionbox.h b/src/conversation/questionbox.h index 1293da6..7ab27be 100644 --- a/src/conversation/questionbox.h +++ b/src/conversation/questionbox.h @@ -1,26 +1,26 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" -#include "../util.h" -#include "../input.h" -#include "../display/common.h" -#include "../display/tilemap.h" -#include "frame.h" -#include "textbox.h" - -#define QUESTION_BOX_OPTIONS_MAX ((TEXTBOX_CHAR_ROWS-1)*2) -#define QUESTION_BOX_CURSOR '>' -#define QUESTION_BOX_QUESTION_SPACES 9 - -extern uint8_t QUESTION_BOX_OPTION_COUNT; -extern uint8_t QUESTION_BOX_OPTION_CURRENT; - -void questionBoxSetOptions(char *title, char **options, uint8_t count); - +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "../util.h" +#include "../input.h" +#include "../sprites/spritefont.h" +#include "../sprites/spriteborder.h" +#include "../sprites/spritetileset.h" +#include "textbox.h" + +#define QUESTION_BOX_OPTIONS_MAX ((TEXTBOX_CHAR_ROWS-1)*2) +#define QUESTION_BOX_CURSOR '>' +#define QUESTION_BOX_QUESTION_SPACES 9 + +extern uint8_t QUESTION_BOX_OPTION_COUNT; +extern uint8_t QUESTION_BOX_OPTION_CURRENT; + +void questionBoxSetOptions(char *title, char **options, uint8_t count); + inline void questionBoxUpdate(); \ No newline at end of file diff --git a/src/conversation/queue.c b/src/conversation/queue.c index 4898b89..6bbe776 100644 --- a/src/conversation/queue.c +++ b/src/conversation/queue.c @@ -1,315 +1,315 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "queue.h" -#include "pause.h" -#include "textbox.h" -#include "fade.h" -#include "../poker/poker.h" - -uint16_t QUEUE_ITEM; - -void conversationQueueDebug() { - conversationTextboxSetText(STR_ERROR); -} - -void conversationQueueBegin() { - QUEUE_ITEM = QUEUE_TAKE_BLINDS; - conversationTextboxSetText(STR_POKER_GAME_START); -} - -void conversationQueueTakeBlinds() { - QUEUE_ITEM = QUEUE_DEAL_CARDS; - conversationTextboxSetText(STR_POKER_GAME_TAKING_BLINDS); -} - -void conversationQueueDealCards() { - QUEUE_ITEM = QUEUE_BEGIN_BETTING; - conversationTextboxSetText(STR_POKER_GAME_CARDS_DEALT); -} - -void conversationQueueBeginBetting() { - pokerturn_t turn; - turn.chips = 0; - // Begin the betting process. First we need to decide if the player or the - // AI is betting, then based on that we decide what to do next. - - // TODO: Actually bet. - if(POKER_PLAYER_BETTER == POKER_HUMAN_INDEX) { - // This is the human player. - BGB_MESSAGE("Player folding."); - conversationTextboxSetText(STR_DEBUG_PLAYER); - POKER_PLAYERS[POKER_PLAYER_BETTER].state |= POKER_PLAYER_STATE_FOLDED; - } else { - // This is an AI player, get their turn. - BGB_MESSAGE("AI turn to bet"); - pokerAi(POKER_PLAYER_BETTER, &turn); - BGB_printf("AI Decided to %u, with %u chips and %u confidence, bluffin: %u", turn.type, turn.chips, turn.confidence, turn.bluff); - - switch(turn.type) { - case POKER_TURN_TYPE_FOLD: - POKER_PLAYERS[POKER_PLAYER_BETTER].state |= POKER_PLAYER_STATE_FOLDED; - conversationTextboxSetText(STR_POKER_GAME_AI_FOLD); - break; - - case POKER_TURN_TYPE_BET: - if(turn.bluff) { - conversationTextboxSetText(STR_POKER_GAME_AI_RAISE_BLUFF); - } else { - conversationTextboxSetText(STR_POKER_GAME_AI_RAISE); - } - break; - - case POKER_TURN_TYPE_CALL: - if(turn.bluff) { - conversationTextboxSetText(STR_POKER_GAME_AI_CALL_BLUFF); - } else { - conversationTextboxSetText(STR_POKER_GAME_AI_CALL); - } - break; - - case POKER_TURN_TYPE_ALL_IN: - if(turn.bluff) { - conversationTextboxSetText(STR_POKER_GAME_AI_ALL_IN_BLUFF); - } else { - conversationTextboxSetText(STR_POKER_GAME_AI_ALL_IN); - } - break; - - case POKER_TURN_TYPE_CHECK: - if(turn.bluff) { - conversationTextboxSetText(STR_POKER_GAME_AI_CHECK_BLUFF); - } else { - conversationTextboxSetText(STR_POKER_GAME_AI_CHECK); - } - break; - } - - // Now we have their turn, decide what to say based on that. - } - - // Update bet state - POKER_PLAYERS[POKER_PLAYER_BETTER].state |= ( - POKER_PLAYER_STATE_HAS_BET_THIS_ROUND - ); - - if(turn.chips > 0) { - pokerBet(POKER_PLAYER_BETTER, turn.chips); - } - - QUEUE_ITEM = QUEUE_NEXT_BETTER; -} - -void conversationQueueNextBetter() { - uint8_t i, j, countStillInGame; - - // Now we decide the next better. - countStillInGame = 0; - for(i = 0; i < POKER_PLAYER_COUNT; i++) { - //In theory I don't think the current better can ever be selected again. - // if(i == POKER_PLAYER_BETTER) continue; // Commented because we now count - - // Next better is the better to the right of the current better. - j = (POKER_PLAYER_BETTER + i) % POKER_PLAYER_COUNT; - if(!pokerDoesPlayerNeedToBet(j)) { - if((POKER_PLAYERS[j].state & (POKER_PLAYER_STATE_FOLDED | POKER_PLAYER_STATE_OUT)) == 0) { - countStillInGame++; - } - continue; - } - - // They haven't bet yet, make them the "next better" - POKER_PLAYER_BETTER = j; - - QUEUE_ITEM = QUEUE_BEGIN_BETTING; - conversationQueueNext(); - return; - } - - // If we reach this point then we either need to begin the betting round, or - // we are going to move to the winning decider. - if(POKER_COMMUNITY_SIZE_MAX == POKER_COMMUNITY_SIZE || countStillInGame == 1) { - QUEUE_ITEM = QUEUE_WINNER_DECIDE; - conversationQueueNext(); - return; - } - - QUEUE_ITEM = QUEUE_FLOP; - conversationQueueNext(); -} - -void conversationQueueFlopTurnRiver() { - uint8_t i, count; - pokerplayer_t *player; - - - switch(POKER_COMMUNITY_SIZE) { - case 0: - count = POKER_COUNT_FLOP; - conversationTextboxSetText(STR_POKER_GAME_CARDS_FLOPPED); - break; - - case POKER_COUNT_FLOP: - count = POKER_COUNT_TURN; - conversationTextboxSetText(STR_POKER_GAME_CARDS_TURNED); - break; - - default: - count = POKER_COUNT_RIVER; - conversationTextboxSetText(STR_POKER_GAME_CARDS_RIVERED); - break; - } - - // Reset each players required to bet state - do { - player = POKER_PLAYERS + i; - player->state &= ~POKER_PLAYER_STATE_HAS_BET_THIS_ROUND; - player->timesRaised = 0; - } while(++i < POKER_PLAYER_COUNT); - - // In reality we'd burn the top card but that would waste some CPU I need. - // Deal the top cards. - for(i = 0; i < count; i++) { - POKER_COMMUNITY[POKER_COMMUNITY_SIZE++] = POKER_DECK[--POKER_DECK_SIZE]; - } - - // Check how many players need to bet, if it's zero then all players are - // either folded or all-in - if(pokerGetRemainingBetterCount() == 0x00) { - if(POKER_COMMUNITY_SIZE == POKER_COMMUNITY_SIZE_MAX) { - QUEUE_ITEM = QUEUE_WINNER_DECIDE; - } else { - QUEUE_ITEM = QUEUE_FLOP; - } - } else { - QUEUE_ITEM = QUEUE_BEGIN_BETTING; - } -} - -void conversationQueueWinnerDecide() { - pokerpot_t *pot; - uint8_t i, j, countOfPotsWithChips, chipsEach; - - QUEUE_ITEM = QUEUE_BEGIN; - - countOfPotsWithChips = 0; - for(i = 0; i < POKER_POT_COUNT; i++) { - pot = POKER_POTS + i; - if(pot->chips == 0) break; - countOfPotsWithChips++; - } - - // TODO: Messages - if(countOfPotsWithChips == 1) { - } else { - } - - // Pots. - for(i = 0; i < countOfPotsWithChips; i++) { - pot = POKER_POTS + i; - pokerWinnerDetermineForPot( - pot, - POKER_WINNERS, - POKER_WINNER_PLAYERS, - &POKER_WINNER_COUNT, - POKER_WINNER_PARTICIPANTS, - &POKER_WINNER_PARTICIPANT_COUNT - ); - - chipsEach = pot->chips / POKER_WINNER_COUNT; - for(j = 0; j < POKER_WINNER_COUNT; j++) { - POKER_PLAYERS[POKER_WINNER_PLAYERS[j]].chips += chipsEach; - // TODO: I can determine rounding error here if I need to, just sub the - // taken chips for each player and when I get to chips remaining less than - // the chipsEach, then reward the rounding offset. - } - } - - // TODO: Decide on a winner for real. - conversationTextboxSetText(STR_DEBUG_WINNER_DECIDED); - - // New Round - pokerNewRound(); -} - -//////////////////////////////////////////////////////////////////////////////// - -queuecallback_t *QUEUE_CALLBACKS[] = { - // 0 - NULL, - &conversationQueueDebug, - NULL, - NULL, - NULL, - - // 5 - &conversationQueueBegin, - NULL, - NULL, - NULL, - NULL, - - // 10 - &conversationQueueTakeBlinds, - NULL, - NULL, - NULL, - NULL, - - // 15 - &conversationQueueDealCards, - NULL, - NULL, - NULL, - NULL, - - // 20 - &conversationQueueBeginBetting, - NULL, - NULL, - NULL, - NULL, - - // 25 - &conversationQueueNextBetter, - NULL, - NULL, - NULL, - NULL, - - // 30 - &conversationQueueFlopTurnRiver, - NULL, - NULL, - NULL, - NULL, - - // 35 - NULL, - NULL, - NULL, - NULL, - NULL, - - // 40 - &conversationQueueWinnerDecide, - // NULL, - // NULL, - // NULL, - // NULL, -}; - -inline void conversationQueueInit() { - QUEUE_ITEM = QUEUE_BEGIN; -} - -inline void conversationQueueNext() { - BGB_printf("Doing %d", QUEUE_ITEM); - if(QUEUE_ITEM > QUEUE_WINNER_DECIDE) return; - if(QUEUE_CALLBACKS[QUEUE_ITEM] == NULL) return; - QUEUE_CALLBACKS[QUEUE_ITEM](); +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "queue.h" +#include "pause.h" +#include "textbox.h" +#include "fade.h" +#include "../poker/poker.h" + +uint16_t QUEUE_ITEM; + +void conversationQueueDebug() { + conversationTextboxSetText(STR_ERROR); +} + +void conversationQueueBegin() { + QUEUE_ITEM = QUEUE_TAKE_BLINDS; + conversationTextboxSetText(STR_POKER_GAME_START); +} + +void conversationQueueTakeBlinds() { + QUEUE_ITEM = QUEUE_DEAL_CARDS; + conversationTextboxSetText(STR_POKER_GAME_TAKING_BLINDS); +} + +void conversationQueueDealCards() { + QUEUE_ITEM = QUEUE_BEGIN_BETTING; + conversationTextboxSetText(STR_POKER_GAME_CARDS_DEALT); +} + +void conversationQueueBeginBetting() { + pokerturn_t turn; + turn.chips = 0; + // Begin the betting process. First we need to decide if the player or the + // AI is betting, then based on that we decide what to do next. + + // TODO: Actually bet. + if(POKER_PLAYER_BETTER == POKER_HUMAN_INDEX) { + // This is the human player. + BGB_MESSAGE("Player folding."); + conversationTextboxSetText(STR_DEBUG_PLAYER); + POKER_PLAYERS[POKER_PLAYER_BETTER].state |= POKER_PLAYER_STATE_FOLDED; + } else { + // This is an AI player, get their turn. + BGB_MESSAGE("AI turn to bet"); + pokerAi(POKER_PLAYER_BETTER, &turn); + BGB_printf("AI Decided to %u, with %u chips and %u confidence, bluffin: %u", turn.type, turn.chips, turn.confidence, turn.bluff); + + switch(turn.type) { + case POKER_TURN_TYPE_FOLD: + POKER_PLAYERS[POKER_PLAYER_BETTER].state |= POKER_PLAYER_STATE_FOLDED; + conversationTextboxSetText(STR_POKER_GAME_AI_FOLD); + break; + + case POKER_TURN_TYPE_BET: + if(turn.bluff) { + conversationTextboxSetText(STR_POKER_GAME_AI_RAISE_BLUFF); + } else { + conversationTextboxSetText(STR_POKER_GAME_AI_RAISE); + } + break; + + case POKER_TURN_TYPE_CALL: + if(turn.bluff) { + conversationTextboxSetText(STR_POKER_GAME_AI_CALL_BLUFF); + } else { + conversationTextboxSetText(STR_POKER_GAME_AI_CALL); + } + break; + + case POKER_TURN_TYPE_ALL_IN: + if(turn.bluff) { + conversationTextboxSetText(STR_POKER_GAME_AI_ALL_IN_BLUFF); + } else { + conversationTextboxSetText(STR_POKER_GAME_AI_ALL_IN); + } + break; + + case POKER_TURN_TYPE_CHECK: + if(turn.bluff) { + conversationTextboxSetText(STR_POKER_GAME_AI_CHECK_BLUFF); + } else { + conversationTextboxSetText(STR_POKER_GAME_AI_CHECK); + } + break; + } + + // Now we have their turn, decide what to say based on that. + } + + // Update bet state + POKER_PLAYERS[POKER_PLAYER_BETTER].state |= ( + POKER_PLAYER_STATE_HAS_BET_THIS_ROUND + ); + + if(turn.chips > 0) { + pokerBet(POKER_PLAYER_BETTER, turn.chips); + } + + QUEUE_ITEM = QUEUE_NEXT_BETTER; +} + +void conversationQueueNextBetter() { + uint8_t i, j, countStillInGame; + + // Now we decide the next better. + countStillInGame = 0; + for(i = 0; i < POKER_PLAYER_COUNT; i++) { + //In theory I don't think the current better can ever be selected again. + // if(i == POKER_PLAYER_BETTER) continue; // Commented because we now count + + // Next better is the better to the right of the current better. + j = (POKER_PLAYER_BETTER + i) % POKER_PLAYER_COUNT; + if(!pokerDoesPlayerNeedToBet(j)) { + if((POKER_PLAYERS[j].state & (POKER_PLAYER_STATE_FOLDED | POKER_PLAYER_STATE_OUT)) == 0) { + countStillInGame++; + } + continue; + } + + // They haven't bet yet, make them the "next better" + POKER_PLAYER_BETTER = j; + + QUEUE_ITEM = QUEUE_BEGIN_BETTING; + conversationQueueNext(); + return; + } + + // If we reach this point then we either need to begin the betting round, or + // we are going to move to the winning decider. + if(POKER_COMMUNITY_SIZE_MAX == POKER_COMMUNITY_SIZE || countStillInGame == 1) { + QUEUE_ITEM = QUEUE_WINNER_DECIDE; + conversationQueueNext(); + return; + } + + QUEUE_ITEM = QUEUE_FLOP; + conversationQueueNext(); +} + +void conversationQueueFlopTurnRiver() { + uint8_t i, count; + pokerplayer_t *player; + + + switch(POKER_COMMUNITY_SIZE) { + case 0: + count = POKER_COUNT_FLOP; + conversationTextboxSetText(STR_POKER_GAME_CARDS_FLOPPED); + break; + + case POKER_COUNT_FLOP: + count = POKER_COUNT_TURN; + conversationTextboxSetText(STR_POKER_GAME_CARDS_TURNED); + break; + + default: + count = POKER_COUNT_RIVER; + conversationTextboxSetText(STR_POKER_GAME_CARDS_RIVERED); + break; + } + + // Reset each players required to bet state + do { + player = POKER_PLAYERS + i; + player->state &= ~POKER_PLAYER_STATE_HAS_BET_THIS_ROUND; + player->timesRaised = 0; + } while(++i < POKER_PLAYER_COUNT); + + // In reality we'd burn the top card but that would waste some CPU I need. + // Deal the top cards. + for(i = 0; i < count; i++) { + POKER_COMMUNITY[POKER_COMMUNITY_SIZE++] = POKER_DECK[--POKER_DECK_SIZE]; + } + + // Check how many players need to bet, if it's zero then all players are + // either folded or all-in + if(pokerGetRemainingBetterCount() == 0x00) { + if(POKER_COMMUNITY_SIZE == POKER_COMMUNITY_SIZE_MAX) { + QUEUE_ITEM = QUEUE_WINNER_DECIDE; + } else { + QUEUE_ITEM = QUEUE_FLOP; + } + } else { + QUEUE_ITEM = QUEUE_BEGIN_BETTING; + } +} + +void conversationQueueWinnerDecide() { + pokerpot_t *pot; + uint8_t i, j, countOfPotsWithChips, chipsEach; + + QUEUE_ITEM = QUEUE_BEGIN; + + countOfPotsWithChips = 0; + for(i = 0; i < POKER_POT_COUNT; i++) { + pot = POKER_POTS + i; + if(pot->chips == 0) break; + countOfPotsWithChips++; + } + + // TODO: Messages + if(countOfPotsWithChips == 1) { + } else { + } + + // Pots. + for(i = 0; i < countOfPotsWithChips; i++) { + pot = POKER_POTS + i; + pokerWinnerDetermineForPot( + pot, + POKER_WINNERS, + POKER_WINNER_PLAYERS, + &POKER_WINNER_COUNT, + POKER_WINNER_PARTICIPANTS, + &POKER_WINNER_PARTICIPANT_COUNT + ); + + chipsEach = pot->chips / POKER_WINNER_COUNT; + for(j = 0; j < POKER_WINNER_COUNT; j++) { + POKER_PLAYERS[POKER_WINNER_PLAYERS[j]].chips += chipsEach; + // TODO: I can determine rounding error here if I need to, just sub the + // taken chips for each player and when I get to chips remaining less than + // the chipsEach, then reward the rounding offset. + } + } + + // TODO: Decide on a winner for real. + conversationTextboxSetText(STR_DEBUG_WINNER_DECIDED); + + // New Round + pokerNewRound(); +} + +//////////////////////////////////////////////////////////////////////////////// + +queuecallback_t *QUEUE_CALLBACKS[] = { + // 0 + NULL, + &conversationQueueDebug, + NULL, + NULL, + NULL, + + // 5 + &conversationQueueBegin, + NULL, + NULL, + NULL, + NULL, + + // 10 + &conversationQueueTakeBlinds, + NULL, + NULL, + NULL, + NULL, + + // 15 + &conversationQueueDealCards, + NULL, + NULL, + NULL, + NULL, + + // 20 + &conversationQueueBeginBetting, + NULL, + NULL, + NULL, + NULL, + + // 25 + &conversationQueueNextBetter, + NULL, + NULL, + NULL, + NULL, + + // 30 + &conversationQueueFlopTurnRiver, + NULL, + NULL, + NULL, + NULL, + + // 35 + NULL, + NULL, + NULL, + NULL, + NULL, + + // 40 + &conversationQueueWinnerDecide, + // NULL, + // NULL, + // NULL, + // NULL, +}; + +inline void conversationQueueInit() { + QUEUE_ITEM = QUEUE_BEGIN; +} + +inline void conversationQueueNext() { + BGB_printf("Doing %d", QUEUE_ITEM); + if(QUEUE_ITEM > QUEUE_WINNER_DECIDE) return; + if(QUEUE_CALLBACKS[QUEUE_ITEM] == NULL) return; + QUEUE_CALLBACKS[QUEUE_ITEM](); } \ No newline at end of file diff --git a/src/conversation/queue.h b/src/conversation/queue.h index 64cd3f9..6ef2a94 100644 --- a/src/conversation/queue.h +++ b/src/conversation/queue.h @@ -1,27 +1,27 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" -#include "../strings.h" - -typedef void queuecallback_t(); - -extern uint16_t QUEUE_ITEM; -extern queuecallback_t *QUEUE_CALLBACKS[]; - -#define QUEUE_DEBUG 1 -#define QUEUE_BEGIN 5 -#define QUEUE_TAKE_BLINDS 10 -#define QUEUE_DEAL_CARDS 15 -#define QUEUE_BEGIN_BETTING 20 -#define QUEUE_NEXT_BETTER 25 -#define QUEUE_FLOP 30 -#define QUEUE_WINNER_DECIDE 40 - -inline void conversationQueueInit(); +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "../strings.h" + +typedef void queuecallback_t(); + +extern uint16_t QUEUE_ITEM; +extern queuecallback_t *QUEUE_CALLBACKS[]; + +#define QUEUE_DEBUG 1 +#define QUEUE_BEGIN 5 +#define QUEUE_TAKE_BLINDS 10 +#define QUEUE_DEAL_CARDS 15 +#define QUEUE_BEGIN_BETTING 20 +#define QUEUE_NEXT_BETTER 25 +#define QUEUE_FLOP 30 +#define QUEUE_WINNER_DECIDE 40 + +inline void conversationQueueInit(); inline void conversationQueueNext(); \ No newline at end of file diff --git a/src/conversation/textbox.c b/src/conversation/textbox.c index e18f378..634ea11 100644 --- a/src/conversation/textbox.c +++ b/src/conversation/textbox.c @@ -1,150 +1,144 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "textbox.h" - -char TEXTBOX_TEXTS[TEXTBOX_SCROLL_ROWS_MAX * TEXTBOX_CHARS_PER_ROW]; -uint8_t TEXTBOX_ROW_COUNT; -uint8_t TEXTBOX_ROW_TILE_LAST; -uint8_t TEXTBOX_ROW_CURRENT; -uint8_t TEXTBOX_STATE; -uint8_t TEXTBOX_SCROLL; - -inline void conversationTextboxInit() { - uint8_t i; - uint8_t TEXTBOX_TILES[TEXTBOX_TILES_MAX]; - - // Reset textbox state - TEXTBOX_STATE = 0; - - // Setup window data - set_win_data(FONT_DATA_POSITION, FONT_IMAGE_TILES, FONT_IMAGE); - set_win_data(BORDER_DATA_POSITION, BORDER_IMAGE_TILES, BORDER_IMAGE); -} - -void conversationTextboxSetText(char *text) { - uint8_t i, j, k, rowStart, stateFlags; - char c, c2; - - // Begin by filling the textbox text chars with whitespace to begin. - // TODO: I'm pretty sure I can move this lower and optimize. - for(i = 0; i < TEXTBOX_SCROLL_ROWS_MAX * TEXTBOX_CHARS_PER_ROW; i++) { - TEXTBOX_TEXTS[i] = ' '; - } - - // Reset textbox state - TEXTBOX_STATE = TEXTBOX_STATE_VISIBLE; - TEXTBOX_SCROLL = 0; - TEXTBOX_ROW_COUNT = 0; - TEXTBOX_ROW_COUNT = 1; - - // Copy source text to buffer, also determine wordwrapping here. - i = 0, j = 0, rowStart = 0, stateFlags = 0; - while((c = text[i]) != '\0') {// "For each char in string" - if(c == ' ') { - // Scan ahead and look at how many chars remain to the next space. - k = i + 1; - while( - (c2 = text[k]) != '\0' && - c2 != '\n' && - c2 != ' ' && - (k - rowStart) < TEXTBOX_CHARS_PER_ROW - ) k++; - - // IF that number is less than the remaining chars on the current row, - // then treat this space like a newline. - if(k >= (rowStart + TEXTBOX_CHARS_PER_ROW + 1)) { - stateFlags |= 1 << 0; - } - } else if(c == '\n') { - stateFlags |= 1 << 0; - } - - // Do we need to insert newline where we are currently? - if((stateFlags & (1 << 0)) != 0) { - stateFlags &= ~(1 << 0);// Remove newline flag - i++; - rowStart = i;// Update the row start (Should this be i+1?) - //TODO: can I optimize the next line by using rowStart somehow? - j = ((j / TEXTBOX_CHARS_PER_ROW)+1) * TEXTBOX_CHARS_PER_ROW;// Update destination character position. - TEXTBOX_ROW_COUNT++; - continue; - } - - // Insert the character - TEXTBOX_TEXTS[j] = c; - i++; - j++; - } - - // Now we have organized the string nicely we can prep for rendering. Fill the - // tiles with blank chars. - textboxFillBlank(); -} - -inline void textboxFillBlank() { - uint8_t TEXTBOX_TILES[TEXTBOX_WIDTH_IN_TILES * TEXTBOX_HEIGHT_IN_TILES]; - - frameBuffer(TEXTBOX_TILES, TEXTBOX_WIDTH_IN_TILES, TEXTBOX_HEIGHT_IN_TILES, TEXTBOX_TILE_BLANK); - - set_bkg_tiles( - 0x00, TEXTBOX_WIN_Y, - TEXTBOX_WIDTH_IN_TILES, TEXTBOX_HEIGHT_IN_TILES, - TEXTBOX_TILES - ); -} - -inline void conversationTextboxUpdate() { - uint8_t i; - char c; - uint8_t tiles[1]; - - // Is the textbox visible? - if((TEXTBOX_STATE & TEXTBOX_STATE_VISIBLE) == 0) return; - - // Have we finished scrolling? - if(TEXTBOX_STATE & TEXTBOX_STATE_SCROLLED) { - // Is the user trying to go to the next line? - if(INPUT_PRESSED & J_A) { - // First, lets figure out if there's any more text to reveal or not. - if((TEXTBOX_ROW_COUNT - TEXTBOX_ROW_CURRENT) < TEXTBOX_TILES_ROWS) { - TEXTBOX_STATE &= ~TEXTBOX_STATE_VISIBLE; - HIDE_WIN; - conversationQueueNext(); - return; - } - - TEXTBOX_STATE &= ~TEXTBOX_STATE_SCROLLED; - TEXTBOX_SCROLL = 0; - TEXTBOX_ROW_CURRENT += TEXTBOX_TILES_ROWS; - textboxFillBlank(); - } - return; - } - - // Move to the next character. - i = TEXTBOX_ROW_CURRENT * TEXTBOX_CHARS_PER_ROW; - c = TEXTBOX_TEXTS[i+TEXTBOX_SCROLL]; - - if(TEXTBOX_SCROLL == TEXTBOX_CHARS_MAX) { - TEXTBOX_STATE |= TEXTBOX_STATE_SCROLLED; - } else { - tiles[0] = c - TEXTBOX_FONT_START + FONT_DATA_POSITION; - - set_bkg_tiles( - 0x01 + (TEXTBOX_SCROLL % TEXTBOX_CHARS_PER_ROW), - TEXTBOX_WIN_Y + 0x01 + (TEXTBOX_SCROLL / TEXTBOX_CHARS_PER_ROW), - 1, 1, - tiles - ); - - TEXTBOX_SCROLL++; - - // Skip spaces - while(TEXTBOX_SCROLL < TEXTBOX_CHARS_MAX && TEXTBOX_TEXTS[i+TEXTBOX_SCROLL] == ' ') TEXTBOX_SCROLL++; - } +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "textbox.h" + +char TEXTBOX_TEXTS[TEXTBOX_SCROLL_ROWS_MAX * TEXTBOX_CHARS_PER_ROW]; +uint8_t TEXTBOX_ROW_COUNT; +uint8_t TEXTBOX_ROW_TILE_LAST; +uint8_t TEXTBOX_ROW_CURRENT; +uint8_t TEXTBOX_STATE; +uint8_t TEXTBOX_SCROLL; + +inline void conversationTextboxInit() { + // Reset textbox state + TEXTBOX_STATE = 0; +} + +void conversationTextboxSetText(char *text) { + uint8_t i, j, k, rowStart, stateFlags; + char c, c2; + + // Begin by filling the textbox text chars with whitespace to begin. + // TODO: I'm pretty sure I can move this lower and optimize. + for(i = 0; i < TEXTBOX_SCROLL_ROWS_MAX * TEXTBOX_CHARS_PER_ROW; i++) { + TEXTBOX_TEXTS[i] = ' '; + } + + // Reset textbox state + TEXTBOX_STATE = TEXTBOX_STATE_VISIBLE; + TEXTBOX_SCROLL = 0; + TEXTBOX_ROW_COUNT = 0; + TEXTBOX_ROW_COUNT = 1; + + // Copy source text to buffer, also determine wordwrapping here. + i = 0, j = 0, rowStart = 0, stateFlags = 0; + while((c = text[i]) != '\0') {// "For each char in string" + if(c == ' ') { + // Scan ahead and look at how many chars remain to the next space. + k = i + 1; + while( + (c2 = text[k]) != '\0' && + c2 != '\n' && + c2 != ' ' && + (k - rowStart) < TEXTBOX_CHARS_PER_ROW + ) k++; + + // IF that number is less than the remaining chars on the current row, + // then treat this space like a newline. + if(k >= (rowStart + TEXTBOX_CHARS_PER_ROW + 1)) { + stateFlags |= 1 << 0; + } + } else if(c == '\n') { + stateFlags |= 1 << 0; + } + + // Do we need to insert newline where we are currently? + if((stateFlags & (1 << 0)) != 0) { + stateFlags &= ~(1 << 0);// Remove newline flag + i++; + rowStart = i;// Update the row start (Should this be i+1?) + //TODO: can I optimize the next line by using rowStart somehow? + j = ((j / TEXTBOX_CHARS_PER_ROW)+1) * TEXTBOX_CHARS_PER_ROW;// Update destination character position. + TEXTBOX_ROW_COUNT++; + continue; + } + + // Insert the character + TEXTBOX_TEXTS[j] = c; + i++; + j++; + } + + // Now we have organized the string nicely we can prep for rendering. Fill the + // tiles with blank chars. + textboxFillBlank(); +} + +inline void textboxFillBlank() { + uint8_t tiles[TEXTBOX_WIDTH_IN_TILES * TEXTBOX_HEIGHT_IN_TILES]; + + spriteBorderBufferEdges( + tiles, TEXTBOX_WIDTH_IN_TILES, TEXTBOX_HEIGHT_IN_TILES, SPRITE_TILESET_WHITE + ); + + set_bkg_tiles( + 0x00, TEXTBOX_WIN_Y, + TEXTBOX_WIDTH_IN_TILES, TEXTBOX_HEIGHT_IN_TILES, + tiles + ); +} + +inline void conversationTextboxUpdate() { + uint8_t i, tile; + char c; + + // Is the textbox visible? + if((TEXTBOX_STATE & TEXTBOX_STATE_VISIBLE) == 0) return; + + // Have we finished scrolling? + if(TEXTBOX_STATE & TEXTBOX_STATE_SCROLLED) { + // Is the user trying to go to the next line? + if(INPUT_PRESSED & J_A) { + // First, lets figure out if there's any more text to reveal or not. + if((TEXTBOX_ROW_COUNT - TEXTBOX_ROW_CURRENT) < TEXTBOX_TILES_ROWS) { + TEXTBOX_STATE &= ~TEXTBOX_STATE_VISIBLE; + HIDE_WIN; + conversationQueueNext(); + return; + } + + TEXTBOX_STATE &= ~TEXTBOX_STATE_SCROLLED; + TEXTBOX_SCROLL = 0; + TEXTBOX_ROW_CURRENT += TEXTBOX_TILES_ROWS; + textboxFillBlank(); + } + return; + } + + // Move to the next character. + i = TEXTBOX_ROW_CURRENT * TEXTBOX_CHARS_PER_ROW; + c = TEXTBOX_TEXTS[i+TEXTBOX_SCROLL]; + + if(TEXTBOX_SCROLL == TEXTBOX_CHARS_MAX) { + TEXTBOX_STATE |= TEXTBOX_STATE_SCROLLED; + } else { + tile = spriteFontTileFromChar(c); + + set_bkg_tiles( + 0x01 + (TEXTBOX_SCROLL % TEXTBOX_CHARS_PER_ROW), + TEXTBOX_WIN_Y + 0x01 + (TEXTBOX_SCROLL / TEXTBOX_CHARS_PER_ROW), + 1, 1, + &tile + ); + + TEXTBOX_SCROLL++; + + // Skip spaces + while(TEXTBOX_SCROLL < TEXTBOX_CHARS_MAX && TEXTBOX_TEXTS[i+TEXTBOX_SCROLL] == ' ') TEXTBOX_SCROLL++; + } } \ No newline at end of file diff --git a/src/conversation/textbox.h b/src/conversation/textbox.h index f660f18..25b19df 100644 --- a/src/conversation/textbox.h +++ b/src/conversation/textbox.h @@ -1,49 +1,46 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" -#include "../util.h" -#include "../input.h" -#include "../display/common.h" -#include "../display/tilemap.h" -#include "queue.h" -#include "frame.h" - -#define TEXTBOX_STATE_VISIBLE (1 << 0) -#define TEXTBOX_STATE_SCROLLED (1 << 1) -#define TEXTBOX_STATE_IS_QUESTION (1 << 2) - -#define TEXTBOX_WIDTH_IN_TILES 20 -#define TEXTBOX_HEIGHT_IN_TILES 5 -#define TEXTBOX_TILES_MAX (TEXTBOX_WIDTH_IN_TILES * TEXTBOX_HEIGHT_IN_TILES) - -#define TEXTBOX_CHARS_PER_ROW (TEXTBOX_WIDTH_IN_TILES - 2) -#define TEXTBOX_CHAR_ROWS (TEXTBOX_HEIGHT_IN_TILES - 2) -#define TEXTBOX_CHARS_MAX (TEXTBOX_CHAR_ROWS * TEXTBOX_CHARS_PER_ROW) -#define TEXTBOX_BUFFER_MAX (TEXTBOX_CHARS_MAX * 3) - -#define TEXTBOX_TILES_PER_ROW TEXTBOX_WIDTH_IN_TILES -#define TEXTBOX_TILES_ROWS 3 -#define TEXTBOX_TILE_BLANK COMMON_TILE_3 - -#define TEXTBOX_SCROLL_ROWS_MAX 14 - -#define TEXTBOX_FONT_START 33 - -#define TEXTBOX_WIN_Y (18 - TEXTBOX_HEIGHT_IN_TILES) - -extern char TEXTBOX_TEXTS[TEXTBOX_SCROLL_ROWS_MAX * TEXTBOX_CHARS_PER_ROW]; -extern uint8_t TEXTBOX_ROW_COUNT; -extern uint8_t TEXTBOX_ROW_CURRENT; -extern uint8_t TEXTBOX_STATE; -extern uint8_t TEXTBOX_SCROLL; - -inline void conversationTextboxInit(); -void conversationTextboxSetText(char *text); -inline void textboxFillBlank(); +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "../util.h" +#include "../input.h" +#include "../sprites/spriteborder.h" +#include "../sprites/spritefont.h" +#include "../sprites/spritetileset.h" +#include "queue.h" + +#define TEXTBOX_STATE_VISIBLE (1 << 0) +#define TEXTBOX_STATE_SCROLLED (1 << 1) +#define TEXTBOX_STATE_IS_QUESTION (1 << 2) + +#define TEXTBOX_WIDTH_IN_TILES 20 +#define TEXTBOX_HEIGHT_IN_TILES 5 +#define TEXTBOX_TILES_MAX (TEXTBOX_WIDTH_IN_TILES * TEXTBOX_HEIGHT_IN_TILES) + +#define TEXTBOX_CHARS_PER_ROW (TEXTBOX_WIDTH_IN_TILES - 2) +#define TEXTBOX_CHAR_ROWS (TEXTBOX_HEIGHT_IN_TILES - 2) +#define TEXTBOX_CHARS_MAX (TEXTBOX_CHAR_ROWS * TEXTBOX_CHARS_PER_ROW) +#define TEXTBOX_BUFFER_MAX (TEXTBOX_CHARS_MAX * 3) + +#define TEXTBOX_TILES_PER_ROW TEXTBOX_WIDTH_IN_TILES +#define TEXTBOX_TILES_ROWS 3 + +#define TEXTBOX_SCROLL_ROWS_MAX 14 + +#define TEXTBOX_WIN_Y (18 - TEXTBOX_HEIGHT_IN_TILES) + +extern char TEXTBOX_TEXTS[TEXTBOX_SCROLL_ROWS_MAX * TEXTBOX_CHARS_PER_ROW]; +extern uint8_t TEXTBOX_ROW_COUNT; +extern uint8_t TEXTBOX_ROW_CURRENT; +extern uint8_t TEXTBOX_STATE; +extern uint8_t TEXTBOX_SCROLL; + +inline void conversationTextboxInit(); +void conversationTextboxSetText(char *text); +inline void textboxFillBlank(); inline void conversationTextboxUpdate(); \ No newline at end of file diff --git a/src/display/common.c b/src/display/common.c deleted file mode 100644 index c959b46..0000000 --- a/src/display/common.c +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "common.h" - -const uint8_t COMMON_TILES[] = { - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00, - 0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 -}; - -inline void commonTilesInit() { - set_bkg_data(0x00, COMMON_TILE_COUNT, COMMON_TILES); -} \ No newline at end of file diff --git a/src/display/common.h b/src/display/common.h deleted file mode 100644 index 99a2a69..0000000 --- a/src/display/common.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" - -#define COMMON_TILE_COUNT 0x04 - -#define COMMON_TILE_0 0x00 -#define COMMON_TILE_1 0x01 -#define COMMON_TILE_2 0x02 -#define COMMON_TILE_3 0x03 - -// Shades are mapped where each set of 4 colors is mapped to two bits that will -// specify its darkness. Higher = Darker, Lower = Brighter - // 11 11 11 11 -#define COMMON_SHADE_BLACK 0xFF -// 11 11 11 10 -#define COMMON_SHADE_DARKER 0xFE -// 11 11 10 01 -#define COMMON_SHADE_DARK 0xF9 -// 11 10 01 00 -#define COMMON_SHADE_NORMAL 0xE4 -// 10 01 00 00 -#define COMMON_SHADE_BRIGHT 0x90 -// 01 00 00 00 -#define COMMON_SHADE_BRIGHTER 0x40 -// 00 00 00 00 -#define COMMON_SHADE_WHITE 0x00 - -extern const uint8_t COMMON_TILES[]; - -inline void commonTilesInit(); \ No newline at end of file diff --git a/src/display/tilemap.h b/src/display/tilemap.h deleted file mode 100644 index d11227f..0000000 --- a/src/display/tilemap.h +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "FONT.h" -#include "BORDER.h" -// #include "SM.h" - -#define FONT_DATA_POSITION 0x80 -#define BORDER_DATA_POSITION FONT_DATA_POSITION+FONT_IMAGE_TILES -// #define SM_DATA_POSITION BORDER_DATA_POSITION+BORDER_IMAGE_TILES diff --git a/src/input.c b/src/input.c index 3f0274b..462b754 100644 --- a/src/input.c +++ b/src/input.c @@ -1,12 +1,12 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "input.h" - -uint8_t INPUT_STATE = 0x00; -uint8_t INPUT_PRESSED = 0x00; +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "input.h" + +uint8_t INPUT_STATE = 0x00; +uint8_t INPUT_PRESSED = 0x00; uint8_t INPUT_LAST = 0x00; \ No newline at end of file diff --git a/src/input.h b/src/input.h index 7dd9e30..b57fe2d 100644 --- a/src/input.h +++ b/src/input.h @@ -1,13 +1,13 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "libs.h" - -extern uint8_t INPUT_STATE; -extern uint8_t INPUT_PRESSED; +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "libs.h" + +extern uint8_t INPUT_STATE; +extern uint8_t INPUT_PRESSED; extern uint8_t INPUT_LAST; \ No newline at end of file diff --git a/src/libs.h b/src/libs.h index eaca791..2c60d0e 100644 --- a/src/libs.h +++ b/src/libs.h @@ -1,15 +1,15 @@ -/** - * Copyright (c) 2021 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include -#include -#include -#include -#include -#include +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include +#include +#include +#include +#include +#include #include \ No newline at end of file diff --git a/src/main.c b/src/main.c index c583511..55247e5 100644 --- a/src/main.c +++ b/src/main.c @@ -1,218 +1,80 @@ -/** - * Copyright (c) 2021 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "main.h" - -inline uint8_t mainGetChar(char c) { - return c - TEXTBOX_FONT_START + FONT_DATA_POSITION; -} - -inline void mainBufferCard(uint8_t card, uint8_t *tiles) { - uint8_t suit, number; - - if(card >= CARD_DECK_SIZE) { - tiles[0] = mainGetChar('N'); - tiles[1] = mainGetChar('A'); - return; - } - - suit = cardGetSuit(card); - number = cardGetNumber(card); - - switch(suit) { - case CARD_SUIT_CLUBS: - tiles[0] = mainGetChar('C'); - break; - case CARD_SUIT_DIAMONDS: - tiles[0] = mainGetChar('D'); - break; - case CARD_SUIT_HEARTS: - tiles[0] = mainGetChar('H'); - break; - case CARD_SUIT_SPADES: - tiles[0] = mainGetChar('S'); - break; - } - - switch(number) { - case CARD_TWO: - tiles[1] = mainGetChar('2'); - break; - case CARD_THREE: - tiles[1] = mainGetChar('3'); - break; - case CARD_FOUR: - tiles[1] = mainGetChar('4'); - break; - case CARD_FIVE: - tiles[1] = mainGetChar('5'); - break; - case CARD_SIX: - tiles[1] = mainGetChar('6'); - break; - case CARD_SEVEN: - tiles[1] = mainGetChar('7'); - break; - case CARD_EIGHT: - tiles[1] = mainGetChar('8'); - break; - case CARD_NINE: - tiles[1] = mainGetChar('9'); - break; - case CARD_TEN: - tiles[1] = mainGetChar('T'); - break; - case CARD_JACK: - tiles[1] = mainGetChar('J'); - break; - case CARD_QUEEN: - tiles[1] = mainGetChar('Q'); - break; - case CARD_KING: - tiles[1] = mainGetChar('K'); - break; - case CARD_ACE: - tiles[1] = mainGetChar('A'); - break; - } -} - -inline void mainDebugDraw() { - uint8_t j, i, n; - - // DEBUG DRAW - uint8_t tiles[15]; - char buffer[6]; - buffer[0] = '\0'; - - for(j = 0; j < POKER_PLAYER_COUNT; j++) { - n = 0; - - if(j == POKER_PLAYER_BETTER) { - tiles[n++] = mainGetChar('>'); - } else { - tiles[n++] = COMMON_TILE_3; - } - - mainBufferCard(POKER_PLAYERS[j].hand[0], tiles+n); - n+=2; - mainBufferCard(POKER_PLAYERS[j].hand[1], tiles+n); - n+=2; - - tiles[n++] = COMMON_TILE_3; - - if((POKER_PLAYERS[j].state & POKER_PLAYER_STATE_FOLDED) == 0) { - tiles[n++] = COMMON_TILE_3; - } else { - tiles[n++] = mainGetChar('F'); - } - - if((POKER_PLAYERS[j].state & POKER_PLAYER_STATE_HAS_BET_THIS_ROUND) == 0) { - tiles[n++] = COMMON_TILE_3; - } else { - tiles[n++] = mainGetChar('H'); - } - - if((POKER_PLAYERS[j].state & POKER_PLAYER_STATE_OUT) == 0) { - tiles[n++] = COMMON_TILE_3; - } else { - tiles[n++] = mainGetChar('O'); - } - - tiles[n++] = COMMON_TILE_3; - - // Print chips - sprintf(buffer, "%u", (uint16_t)POKER_PLAYERS[j].chips); - for(i = 0; i < strlen(buffer); i++) { - tiles[n++] = mainGetChar(buffer[i]); - } - - while(n < 15) tiles[n++] = COMMON_TILE_3; - set_bkg_tiles(0x00, j, n - 1, 1, tiles); - } - - for(j = 0; j < POKER_COMMUNITY_SIZE_MAX; j++) { - if(j >= POKER_COMMUNITY_SIZE) { - tiles[j*2] = COMMON_TILE_3; - tiles[(j*2) + 1] = COMMON_TILE_3; - } else { - mainBufferCard(POKER_COMMUNITY[j], tiles + (j * 2)); - } - } - - set_bkg_tiles(0x00, POKER_PLAYER_COUNT + 1, POKER_COMMUNITY_SIZE_MAX * 2, 1, tiles); - - - // Print Pot - n = 0; - sprintf(buffer, "%u", (uint16_t)POKER_POTS[POKER_POT_CURRENT].chips); - for(i = 0; i < strlen(buffer); i++) { - tiles[n++] = mainGetChar(buffer[i]); - } - while(n < 5) tiles[n++] = COMMON_TILE_3; - set_bkg_tiles(0x00, POKER_PLAYER_COUNT + 2, n, 1, tiles); -} - -void main() { - int16_t j; - uint8_t filled[GB_BACKGROUND_COLUMNS*GB_BACKGROUND_ROWS]; - - // Set up the GAMEBOY's registers. - disable_interrupts(); - DISPLAY_OFF; - LCDC_REG = LCDCF_OFF | LCDCF_WIN9C00 | LCDCF_BG8800 | LCDCF_BG9800 | LCDCF_BGON; - // Set the background color palette register - BGP_REG = OBP0_REG = OBP1_REG = 0xE4U; - - // Init the random seed - initarand(DIV_REG); - - // Init things - commonTilesInit(); - conversationTextboxInit(); - conversationQueueInit(); - pokerInit(); - - // Fill screen white - for(j = 0; j < GB_BACKGROUND_COLUMNS * GB_BACKGROUND_ROWS; j++) filled[j] = COMMON_TILE_3; - set_bkg_tiles(0x00, 0x00, GB_BACKGROUND_COLUMNS, GB_BACKGROUND_ROWS, filled); - SCX_REG = 0x00; - SCY_REG = 0x00; - - // Now turn the screen on - DISPLAY_ON; - enable_interrupts(); - wait_vbl_done(); - - // Begin game - conversationQueueNext(); - - // Begin the loop - while(1) { - // Perform non-graphical code updates - wait_vbl_done(); - - // Update the input state - INPUT_LAST = INPUT_STATE; - INPUT_STATE = joypad(); - INPUT_PRESSED = (~INPUT_LAST) & INPUT_STATE; - - // Tick time. - timeUpdate(); - - // Update conversation pause effect - conversationPauseUpdate(); - - // Update question box and textbox - questionBoxUpdate(); - conversationTextboxUpdate(); - - // Update conversation fade effect - conversationFadeUpdate(); - mainDebugDraw(); - } +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "main.h" + +void main() { + int16_t j; + uint8_t filled[GB_BACKGROUND_COLUMNS*GB_BACKGROUND_ROWS]; + + // Set up the GAMEBOY's registers. + disable_interrupts(); + DISPLAY_OFF; + LCDC_REG = LCDCF_OFF | LCDCF_BG8000 | LCDCF_BG9800 | LCDCF_BGON; + + // Set the background color palette register + BGP_REG = OBP0_REG = OBP1_REG = 0xE4U; + + // Init the random seed + initarand(DIV_REG); + + // Init things + spriteTilesetBuffer(); + spriteFontBuffer(); + spriteBorderBuffer(); + spriteCardsBuffer(); + + conversationTextboxInit(); + conversationQueueInit(); + pokerInit(); + + // Fill screen white + for(j = 0; j < GB_BACKGROUND_COLUMNS * GB_BACKGROUND_ROWS; j++) { + filled[j] = SPRITE_TILESET_WHITE; + } + set_bkg_tiles(0x00, 0x00, GB_BACKGROUND_COLUMNS, GB_BACKGROUND_ROWS, filled); + SCX_REG = 0x00; + SCY_REG = 0x00; + + // Card Test + uint8_t cardTiles[SPRITE_CARD_TILE_COUNT]; + spriteCardBufferTiles(cardTiles, CARD_HEARTS_TWO); + set_bkg_tiles(0x00, 0x00, SPRITE_CARD_WIDTH, SPRITE_CARD_HEIGHT, cardTiles); + + // Now turn the screen on + DISPLAY_ON; + enable_interrupts(); + wait_vbl_done(); + + // Begin game + conversationQueueNext(); + + // Begin the loop + while(1) { + // Perform non-graphical code updates + wait_vbl_done(); + + // Update the input state + INPUT_LAST = INPUT_STATE; + INPUT_STATE = joypad(); + INPUT_PRESSED = (~INPUT_LAST) & INPUT_STATE; + + // Tick time. + timeUpdate(); + + // Update conversation pause effect + conversationPauseUpdate(); + + // Update question box and textbox + questionBoxUpdate(); + conversationTextboxUpdate(); + + // Update conversation fade effect + conversationFadeUpdate(); + // mainDebugDraw(); + } } \ No newline at end of file diff --git a/src/main.h b/src/main.h index 1fc3597..80b6fd8 100644 --- a/src/main.h +++ b/src/main.h @@ -1,28 +1,28 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once - -#include "libs.h" -#include "time.h" - -#include "SM.h" - -#include "display/common.h" -#include "display/tilemap.h" -#include "poker/poker.h" -#include "poker/card.h" -#include "conversation/fade.h" -#include "conversation/pause.h" -#include "conversation/queue.h" -#include "conversation/textbox.h" -#include "conversation/questionbox.h" - -#define GB_BACKGROUND_COLUMNS 0x20 -#define GB_BACKGROUND_ROWS 0x20 - +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once + +#include "libs.h" +#include "time.h" + +#include "SM.h" + +#include "poker/poker.h" +#include "poker/card.h" +#include "conversation/fade.h" +#include "conversation/pause.h" +#include "conversation/queue.h" +#include "conversation/textbox.h" +#include "conversation/questionbox.h" + +#include "sprites/spritecards.h" + +#define GB_BACKGROUND_COLUMNS 0x20 +#define GB_BACKGROUND_ROWS 0x20 + void main(); \ No newline at end of file diff --git a/src/poker/card.c b/src/poker/card.c index 813f134..b10cec3 100644 --- a/src/poker/card.c +++ b/src/poker/card.c @@ -1,50 +1,50 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "card.h" - -inline uint8_t cardGetNumber(uint8_t card) { - return card % CARD_COUNT_PER_SUIT; -} - -inline uint8_t cardGetSuit(uint8_t card) { - return card / CARD_COUNT_PER_SUIT; -} - -inline uint8_t cardGet(uint8_t number, uint8_t suit) { - return (suit * CARD_COUNT_PER_SUIT) + number; -} - -inline uint8_t cardContains(uint8_t *hand, uint8_t length, uint8_t card) { - uint8_t i; - for(i = 0; i < length; i++) { - if(hand[i] == card) return i; - } - return 0xFF; -} - -inline uint8_t cardContainsNumber(uint8_t *hand,uint8_t length,uint8_t number) { - uint8_t i; - for(i = 0; i < length; i++) { - if(cardGetNumber(hand[i]) == number) return i; - } - return 0xFF; -} - -inline uint8_t cardCountPairs( - uint8_t *in, uint8_t inCount, uint8_t number, uint8_t out[CARD_SUIT_COUNT] -) { - uint8_t i, count; - - count = 0; - for(i = 0; i < inCount; i++) {// "For each suit" - if(cardGetNumber(in[i]) != number) continue; - out[count++] = i; - } - - return count; +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "card.h" + +inline uint8_t cardGetNumber(uint8_t card) { + return card % CARD_COUNT_PER_SUIT; +} + +inline uint8_t cardGetSuit(uint8_t card) { + return card / CARD_COUNT_PER_SUIT; +} + +inline uint8_t cardGet(uint8_t number, uint8_t suit) { + return (suit * CARD_COUNT_PER_SUIT) + number; +} + +inline uint8_t cardContains(uint8_t *hand, uint8_t length, uint8_t card) { + uint8_t i; + for(i = 0; i < length; i++) { + if(hand[i] == card) return i; + } + return 0xFF; +} + +inline uint8_t cardContainsNumber(uint8_t *hand,uint8_t length,uint8_t number) { + uint8_t i; + for(i = 0; i < length; i++) { + if(cardGetNumber(hand[i]) == number) return i; + } + return 0xFF; +} + +inline uint8_t cardCountPairs( + uint8_t *in, uint8_t inCount, uint8_t number, uint8_t out[CARD_SUIT_COUNT] +) { + uint8_t i, count; + + count = 0; + for(i = 0; i < inCount; i++) {// "For each suit" + if(cardGetNumber(in[i]) != number) continue; + out[count++] = i; + } + + return count; } \ No newline at end of file diff --git a/src/poker/card.h b/src/poker/card.h index 9dedc49..c095a91 100644 --- a/src/poker/card.h +++ b/src/poker/card.h @@ -1,117 +1,117 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" -#include "../util.h" - -//////////////////////////////////////////////////////////////////////////////// -// Cards -//////////////////////////////////////////////////////////////////////////////// - -// Aces -#define CARD_CLUBS_TWO 0x00 -#define CARD_CLUBS_THREE 0x01 -#define CARD_CLUBS_FOUR 0x02 -#define CARD_CLUBS_FIVE 0x03 -#define CARD_CLUBS_SIX 0x04 -#define CARD_CLUBS_SEVEN 0x05 -#define CARD_CLUBS_EIGHT 0x06 -#define CARD_CLUBS_NINE 0x07 -#define CARD_CLUBS_TEN 0x08 -#define CARD_CLUBS_JACK 0x09 -#define CARD_CLUBS_QUEEN 0x0A -#define CARD_CLUBS_KING 0x0B -#define CARD_CLUBS_ACE 0x0C - -// Diamonds -#define CARD_DIAMONDS_TWO 0x0D -#define CARD_DIAMONDS_THREE 0x0E -#define CARD_DIAMONDS_FOUR 0x0F -#define CARD_DIAMONDS_FIVE 0x10 -#define CARD_DIAMONDS_SIX 0x11 -#define CARD_DIAMONDS_SEVEN 0x12 -#define CARD_DIAMONDS_EIGHT 0x13 -#define CARD_DIAMONDS_NINE 0x14 -#define CARD_DIAMONDS_TEN 0x15 -#define CARD_DIAMONDS_JACK 0x16 -#define CARD_DIAMONDS_QUEEN 0x17 -#define CARD_DIAMONDS_KING 0x18 -#define CARD_DIAMONDS_ACE 0x19 - -// Hearts -#define CARD_HEARTS_TWO 0x1A -#define CARD_HEARTS_THREE 0x1B -#define CARD_HEARTS_FOUR 0x1C -#define CARD_HEARTS_FIVE 0x1D -#define CARD_HEARTS_SIX 0x1E -#define CARD_HEARTS_SEVEN 0x1F -#define CARD_HEARTS_EIGHT 0x20 -#define CARD_HEARTS_NINE 0x21 -#define CARD_HEARTS_TEN 0x22 -#define CARD_HEARTS_JACK 0x23 -#define CARD_HEARTS_QUEEN 0x24 -#define CARD_HEARTS_KING 0x25 -#define CARD_HEARTS_ACE 0x26 - -// Spades -#define CARD_SPADES_TWO 0x27 -#define CARD_SPADES_THREE 0x28 -#define CARD_SPADES_FOUR 0x29 -#define CARD_SPADES_FIVE 0x2A -#define CARD_SPADES_SIX 0x2B -#define CARD_SPADES_SEVEN 0x2C -#define CARD_SPADES_EIGHT 0x2D -#define CARD_SPADES_NINE 0x2E -#define CARD_SPADES_TEN 0x2F -#define CARD_SPADES_JACK 0x30 -#define CARD_SPADES_QUEEN 0x31 -#define CARD_SPADES_KING 0x32 -#define CARD_SPADES_ACE 0x33 - -//////////////////////////////////////////////////////////////////////////////// -// Suits -//////////////////////////////////////////////////////////////////////////////// -#define CARD_SUIT_CLUBS 0x00 -#define CARD_SUIT_DIAMONDS 0x01 -#define CARD_SUIT_HEARTS 0x02 -#define CARD_SUIT_SPADES 0x03 - -//////////////////////////////////////////////////////////////////////////////// -// Card numbers -//////////////////////////////////////////////////////////////////////////////// -#define CARD_TWO 0x00 -#define CARD_THREE 0x01 -#define CARD_FOUR 0x02 -#define CARD_FIVE 0x03 -#define CARD_SIX 0x04 -#define CARD_SEVEN 0x05 -#define CARD_EIGHT 0x06 -#define CARD_NINE 0x07 -#define CARD_TEN 0x08 -#define CARD_JACK 0x09 -#define CARD_QUEEN 0x0A -#define CARD_KING 0x0B -#define CARD_ACE 0x0C - -/** Count of cards in each suit */ -#define CARD_COUNT_PER_SUIT 13 - -/** Count of suits */ -#define CARD_SUIT_COUNT 4 - -/** Standard Card Deck Size */ -#define CARD_DECK_SIZE 52 - -inline uint8_t cardGetNumber(uint8_t card); -inline uint8_t cardGetSuit(uint8_t card); -inline uint8_t cardGet(uint8_t card, uint8_t suit); -inline uint8_t cardContains(uint8_t *hand, uint8_t length, uint8_t card); -inline uint8_t cardContainsNumber(uint8_t *hand,uint8_t length,uint8_t number); -inline uint8_t cardCountPairs( - uint8_t *in, uint8_t inCount, uint8_t number, uint8_t out[CARD_SUIT_COUNT] +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "../util.h" + +//////////////////////////////////////////////////////////////////////////////// +// Cards +//////////////////////////////////////////////////////////////////////////////// + +// Aces +#define CARD_CLUBS_TWO 0x00 +#define CARD_CLUBS_THREE 0x01 +#define CARD_CLUBS_FOUR 0x02 +#define CARD_CLUBS_FIVE 0x03 +#define CARD_CLUBS_SIX 0x04 +#define CARD_CLUBS_SEVEN 0x05 +#define CARD_CLUBS_EIGHT 0x06 +#define CARD_CLUBS_NINE 0x07 +#define CARD_CLUBS_TEN 0x08 +#define CARD_CLUBS_JACK 0x09 +#define CARD_CLUBS_QUEEN 0x0A +#define CARD_CLUBS_KING 0x0B +#define CARD_CLUBS_ACE 0x0C + +// Diamonds +#define CARD_DIAMONDS_TWO 0x0D +#define CARD_DIAMONDS_THREE 0x0E +#define CARD_DIAMONDS_FOUR 0x0F +#define CARD_DIAMONDS_FIVE 0x10 +#define CARD_DIAMONDS_SIX 0x11 +#define CARD_DIAMONDS_SEVEN 0x12 +#define CARD_DIAMONDS_EIGHT 0x13 +#define CARD_DIAMONDS_NINE 0x14 +#define CARD_DIAMONDS_TEN 0x15 +#define CARD_DIAMONDS_JACK 0x16 +#define CARD_DIAMONDS_QUEEN 0x17 +#define CARD_DIAMONDS_KING 0x18 +#define CARD_DIAMONDS_ACE 0x19 + +// Hearts +#define CARD_HEARTS_TWO 0x1A +#define CARD_HEARTS_THREE 0x1B +#define CARD_HEARTS_FOUR 0x1C +#define CARD_HEARTS_FIVE 0x1D +#define CARD_HEARTS_SIX 0x1E +#define CARD_HEARTS_SEVEN 0x1F +#define CARD_HEARTS_EIGHT 0x20 +#define CARD_HEARTS_NINE 0x21 +#define CARD_HEARTS_TEN 0x22 +#define CARD_HEARTS_JACK 0x23 +#define CARD_HEARTS_QUEEN 0x24 +#define CARD_HEARTS_KING 0x25 +#define CARD_HEARTS_ACE 0x26 + +// Spades +#define CARD_SPADES_TWO 0x27 +#define CARD_SPADES_THREE 0x28 +#define CARD_SPADES_FOUR 0x29 +#define CARD_SPADES_FIVE 0x2A +#define CARD_SPADES_SIX 0x2B +#define CARD_SPADES_SEVEN 0x2C +#define CARD_SPADES_EIGHT 0x2D +#define CARD_SPADES_NINE 0x2E +#define CARD_SPADES_TEN 0x2F +#define CARD_SPADES_JACK 0x30 +#define CARD_SPADES_QUEEN 0x31 +#define CARD_SPADES_KING 0x32 +#define CARD_SPADES_ACE 0x33 + +//////////////////////////////////////////////////////////////////////////////// +// Suits +//////////////////////////////////////////////////////////////////////////////// +#define CARD_SUIT_CLUBS 0x00 +#define CARD_SUIT_DIAMONDS 0x01 +#define CARD_SUIT_HEARTS 0x02 +#define CARD_SUIT_SPADES 0x03 + +//////////////////////////////////////////////////////////////////////////////// +// Card numbers +//////////////////////////////////////////////////////////////////////////////// +#define CARD_TWO 0x00 +#define CARD_THREE 0x01 +#define CARD_FOUR 0x02 +#define CARD_FIVE 0x03 +#define CARD_SIX 0x04 +#define CARD_SEVEN 0x05 +#define CARD_EIGHT 0x06 +#define CARD_NINE 0x07 +#define CARD_TEN 0x08 +#define CARD_JACK 0x09 +#define CARD_QUEEN 0x0A +#define CARD_KING 0x0B +#define CARD_ACE 0x0C + +/** Count of cards in each suit */ +#define CARD_COUNT_PER_SUIT 13 + +/** Count of suits */ +#define CARD_SUIT_COUNT 4 + +/** Standard Card Deck Size */ +#define CARD_DECK_SIZE 52 + +inline uint8_t cardGetNumber(uint8_t card); +inline uint8_t cardGetSuit(uint8_t card); +inline uint8_t cardGet(uint8_t card, uint8_t suit); +inline uint8_t cardContains(uint8_t *hand, uint8_t length, uint8_t card); +inline uint8_t cardContainsNumber(uint8_t *hand,uint8_t length,uint8_t number); +inline uint8_t cardCountPairs( + uint8_t *in, uint8_t inCount, uint8_t number, uint8_t out[CARD_SUIT_COUNT] ); \ No newline at end of file diff --git a/src/poker/player.h b/src/poker/player.h index f8734e8..f2989bd 100644 --- a/src/poker/player.h +++ b/src/poker/player.h @@ -1,28 +1,28 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" -#include "card.h" - -#define POKER_PLAYER_COUNT_MAX 5 -#define POKER_PLAYER_HAND_SIZE_MAX 2 - -#define POKER_PLAYER_STATE_FOLDED (1 << 0) -#define POKER_PLAYER_STATE_OUT (1 << 1) -#define POKER_PLAYER_STATE_HAS_BET_THIS_ROUND (1 << 2) -// #define POKER_PLAYER_STATE_SHOWING 1 << 3 - -typedef struct { - uint16_t chips; - uint8_t hand[POKER_PLAYER_HAND_SIZE_MAX]; - uint8_t state; - uint8_t timesRaised; -} pokerplayer_t; - -extern pokerplayer_t POKER_PLAYERS[]; +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "card.h" + +#define POKER_PLAYER_COUNT_MAX 5 +#define POKER_PLAYER_HAND_SIZE_MAX 2 + +#define POKER_PLAYER_STATE_FOLDED (1 << 0) +#define POKER_PLAYER_STATE_OUT (1 << 1) +#define POKER_PLAYER_STATE_HAS_BET_THIS_ROUND (1 << 2) +// #define POKER_PLAYER_STATE_SHOWING 1 << 3 + +typedef struct { + uint16_t chips; + uint8_t hand[POKER_PLAYER_HAND_SIZE_MAX]; + uint8_t state; + uint8_t timesRaised; +} pokerplayer_t; + +extern pokerplayer_t POKER_PLAYERS[]; extern uint8_t POKER_PLAYER_COUNT; \ No newline at end of file diff --git a/src/poker/poker.c b/src/poker/poker.c index be098ba..50efe59 100644 --- a/src/poker/poker.c +++ b/src/poker/poker.c @@ -1,789 +1,789 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "poker.h" - -pokerplayer_t POKER_PLAYERS[POKER_PLAYER_COUNT_MAX]; - -uint8_t POKER_DECK[CARD_DECK_SIZE]; -uint8_t POKER_DECK_SIZE; -uint8_t POKER_COMMUNITY[POKER_COMMUNITY_SIZE_MAX]; -uint8_t POKER_COMMUNITY_SIZE; - -uint8_t POKER_PLAYER_DEALER; -uint8_t POKER_PLAYER_SMALL_BLIND; -uint8_t POKER_PLAYER_BIG_BLIND; -uint8_t POKER_PLAYER_BETTER; - -uint16_t POKER_GAME_BLINDS_CURRENT; - -pokerpot_t POKER_POTS[POKER_POT_COUNT_MAX]; -uint8_t POKER_POT_CURRENT; -uint8_t POKER_POT_COUNT; - -pokerplayerwinning_t POKER_WINNERS[POKER_PLAYER_COUNT_MAX]; -uint8_t POKER_WINNER_PLAYERS[POKER_PLAYER_COUNT_MAX]; -uint8_t POKER_WINNER_PARTICIPANTS[POKER_PLAYER_COUNT_MAX]; -uint8_t POKER_WINNER_COUNT; -uint8_t POKER_WINNER_PARTICIPANT_COUNT; -uint8_t POKER_PLAYER_COUNT; - -void pokerInit() { - uint8_t i; - - // Set up the initial state. - // TODO: Should this be randomized? - POKER_PLAYER_DEALER = 0; - POKER_GAME_BLINDS_CURRENT = 10; - POKER_PLAYER_COUNT = POKER_PLAYER_COUNT_MAX; - - // Set up players - for(i = 0; i < POKER_PLAYER_COUNT; i++) { - POKER_PLAYERS[i].chips = 10000; - POKER_PLAYERS[i].state = 0; - } - - // Reset the round state (For the first round) - pokerNewRound(); -} - -void pokerNewRound() { - uint8_t i, j, k; - uint8_t found; - pokerplayer_t *player; - - // Reset round state - POKER_COMMUNITY_SIZE = 0; - POKER_POT_COUNT = 0; - POKER_POT_CURRENT = 0; - - // Reset the pots. - for(i = 0; i < POKER_POT_COUNT_MAX; i++) { - POKER_POTS[i].chips = 0; - POKER_POTS[i].call = 0; - for(j = 0; j < POKER_PLAYER_COUNT; j++) { - POKER_POTS[i].players[j] = 0; - } - } - - // Fill deck - for(i = 0; i < CARD_DECK_SIZE; i++) POKER_DECK[i] = i; - POKER_DECK_SIZE = CARD_DECK_SIZE; - - // Shuffle Deck - for(i = POKER_DECK_SIZE-1; i > 0; i--) { - k = rand(); - j = k % (i+1); - k = POKER_DECK[i]; - POKER_DECK[i] = POKER_DECK[j]; - POKER_DECK[j] = k; - } - - // Reset Players and decide new blinds. - found = 0; - POKER_PLAYER_DEALER++; - - // Update players for the round. - for(i = 0; i < POKER_PLAYER_COUNT; i++) { - player = POKER_PLAYERS + i; - player->timesRaised = 0; - player->state &= ~( - POKER_PLAYER_STATE_FOLDED | - POKER_PLAYER_STATE_HAS_BET_THIS_ROUND - ); - - // Update out state. - if(player->chips == 0) player->state |= POKER_PLAYER_STATE_OUT; - - // Is the player out? - if((player->state & POKER_PLAYER_STATE_OUT) != 0) continue; - - // Have we found the dealer, small blind, and big blind players? - if(found != 3) { - k = (POKER_PLAYER_DEALER + i) % POKER_PLAYER_COUNT; - - if(found == 0) {// Have we found the dealer? - POKER_PLAYER_DEALER = i; - found++; - } else if(found == 1) {// Have we found the small blind? - POKER_PLAYER_SMALL_BLIND = i; - found++; - } else if(found == 2) {// Have we found the big blind? - POKER_PLAYER_BIG_BLIND = i; - found++; - } - } - - // Deal two cards to the player. - for(j = 0; j < POKER_PLAYER_HAND_SIZE_MAX; j++) { - player->hand[j] = POKER_DECK[--POKER_DECK_SIZE]; - } - } - - // Take blinds - // TODO: I need to make sure the blind players even have the chips to blind. - pokerBet(POKER_PLAYER_SMALL_BLIND, POKER_GAME_BLINDS_CURRENT); - pokerBet(POKER_PLAYER_BIG_BLIND, (POKER_GAME_BLINDS_CURRENT*2)); - - // Set the initial better, we set this to the BIG BLIND player because we will - // cycle to the "next better" as soon as the game starts. - POKER_PLAYER_BETTER = POKER_PLAYER_BIG_BLIND; -} - -inline void pokerBet(uint8_t player, uint16_t amount) { - // TODO: This may become a function because if a player doesn't have enough - // chips to bet to the active pot, then the pot needs to autosplit, take those - // who have bet into the pot up to the amount that the player betting can bet, - // and push them into a new pot. - // There also needs to be a limit on this, for example; - // player 0 has $1200, and bets $1000, they can't bet more than that ever. - // player 1 has $1000, and bets all of it. The remanin - // player 2 has $700, and bets all o it. A new $300 sidepot auto creates - // player 3 has $500 and bets all of it, Another sidepot with $200 is auto made. - if(POKER_POT_CURRENT == POKER_POT_COUNT) POKER_POT_COUNT++; - - POKER_PLAYERS[player].chips -= amount; - POKER_POTS[POKER_POT_CURRENT].chips += amount; - POKER_POTS[POKER_POT_CURRENT].players[player] += amount; - POKER_POTS[POKER_POT_CURRENT].call = MATH_MAX( - amount, POKER_POTS[POKER_POT_CURRENT].players[player] - ); -} - -inline uint8_t pokerGetCallBet(uint8_t player) { - return ( - POKER_POTS[POKER_POT_CURRENT].call - - POKER_POTS[POKER_POT_CURRENT].players[player] - ); -} - -void pokerAi(uint8_t player, pokerturn_t *turn) { - uint8_t i, cardNumber0, cardNumber1, suitNumber0, suitNumber1; - uint16_t callBet, maxBet, amount, bluffBet; - uint8_t random;// TODO: Determine type. - uint16_t confidence, expectedGain, potOdds; - pokerplayerwinning_t winning; - pokerplayer_t *plyr = POKER_PLAYERS + player; - - // The following logic is heavily inspired by; - // https://github.com/gorel/C-Poker-AI/blob/master/src/common/pokerai.c - // But with some changes and smarts added by me. The original source code will - // essentially just run a crap tun of simulated games and get the times that - // they are expected to win from those games, but I'm just going to use the - // odds of the hand being the winning hand. - - // Is this preflop? - if(POKER_COMMUNITY_SIZE == 0) { - // Get the hand weight - cardNumber0 = cardGetNumber(plyr->hand[0]); - cardNumber1 = cardGetNumber(plyr->hand[1]); - suitNumber0 = cardGetSuit(plyr->hand[0]); - suitNumber1 = cardGetSuit(plyr->hand[1]); - - // Get delta between cards - i = MATH_ABS(cardNumber0 - cardNumber1); - - // Get card weight - confidence = cardNumber0 + cardNumber1; - if(cardNumber0 == cardNumber1) {// Pairs - confidence += 6; - } else if(suitNumber0 == suitNumber1) {// Same suit - confidence += 4; - } - - // Get difference from cards for guessing flush - if(i > 4) { - confidence -= 4; - } else if(i > 2) { - confidence -= i; - } - - // Confidence is now a value between 0-30 (inclusive). Multiply by 1000 - confidence = (confidence * 1000) / 30; - // Now do over 30 to get the value represented as 0-1000 - } else { - // Simulate my hand being the winning hand, use that as the confidence - // TODO: Repurpose old code lmao. Just take it from Dawn-C - BGB_printf("Holy shit you guys"); - pokerWinnerGetForPlayer(player, &winning); - BGB_printf("Winning type %u", winning.type); - confidence = pokerWinnerGetTypeConfidence(winning.type); - BGB_printf("Confidence %u", confidence); - } - - // Now we know how confident the AI is, let's put a chip value to that weight - // How many chips to call? - callBet = pokerGetCallBet(player); - BGB_printf("Callbet %u", callBet); - - - // Do they need chips to call, or is it possible to check? - if(callBet > 0) { - // Work out how many chips the player could possibly win. This is a number - // between 0 and player count * initial chips, at the time of writing that - // is around 50,000. - expectedGain = 0; - for(i = 0; i < POKER_POT_COUNT; i++) { - if(POKER_POTS[i].players[player] == 0) break; - expectedGain += POKER_POTS[i].chips; - } - - - // Now work out the pot odds, this is basically "how many times the callbet - // could I win if I win this hand". e.g. Desirable hands will have an - // expected gain much higher than the callbet. - potOdds = plyr->chips / 1000; - potOdds = MATH_MAX((callBet + expectedGain), 1) / MATH_MAX(potOdds, 1); - - BGB_printf("Expected Gain and Pot Odds %u, %u", expectedGain, potOdds); - } else { - // If the call bet is zero then there's fairly equal odds, so let's just - // take the chances out of the remainig player count. - potOdds = 1000 / pokerGetRemainingBetterCount() * 2;// 0 - 1000 - BGB_printf("Pot Odds %u", potOdds); - } - - // Now determine the expected ROI - //TODO: I think these values are a bit odd. - expectedGain = (confidence*100) / (potOdds / 10); - BGB_printf("Expected Gains (2) %u", expectedGain); - - // Now get a random number 0-100. - random = rand(); - random = random % 100; - - // Determine the max bet that the AI is willing to make. The max bet is - // basically how high the AI is willing to bet. - maxBet = plyr->chips;// TODO: Replace with below code and test, just running - // With this for now. - // maxBet = plyr->chips / MATH_MAX(random / 10, 1); - // maxBet -= callBet; - // BGB_printf("Rand %u, Max Bet %u", random, callBet); - - // Determine what's a good bluff bet. - // TODO: not float - bluffBet = maxBet; - // bluffBet = ((random * 100) / maxBet) * plyr->chips; - - // Now prep the output - turn->bluff = false; - amount = 0; - - BGB_printf("Random Dice %u", random); - - - // Now the actual AI can happen. This is basically a weight to confidence - // ratio. The higher the gains and the confidence then the more likely the AI - // is to betting. There are also bluff chances within here. - if(expectedGain < 800 && confidence < 800) { - if(random < 95) { - amount = 0; - } else { - amount = bluffBet; - turn->bluff = true; - } - } else if ((expectedGain < 1000 && confidence < 850) || confidence < 100) { - if (random < 80) { - amount = 0; - } else if(random < 5) { - amount = callBet; - turn->bluff = true; - } else { - amount = bluffBet; - turn->bluff = true; - } - } else if ((expectedGain < 1300 && confidence < 900) || confidence < 500) { - if (random < 60 || confidence < 500) { - amount = callBet; - } else { - amount = maxBet; - } - } else if (confidence < 950 || POKER_COMMUNITY_SIZE < 0x04) { - if(random < 20) { - amount = callBet; - } else { - amount = maxBet; - } - } else { - // TODO: check this - amount = (plyr->chips - callBet) * 9 / 10; - } - - // If this is the first round... make it a lot less likely I'll bet - if(POKER_COMMUNITY_SIZE == 0x00 && amount > callBet) { - if(random > 5) amount = callBet; - } - - - // Did we actually bet? - if(amount > 0) { - // Let's not get caught in a raising loop with AI. - if(plyr->timesRaised >= POKER_TURN_MAX_RAISES) amount = callBet; - amount = MATH_MAX(amount, callBet); - amount = MATH_MIN(amount, plyr->chips); - turn->chips = amount; - turn->confidence = confidence; - - if(plyr->chips == amount) { - turn->type = POKER_TURN_TYPE_ALL_IN; - } else if(amount == callBet) { - turn->type = POKER_TURN_TYPE_CALL; - } else { - turn->type = POKER_TURN_TYPE_BET; - } - } else if(pokerCanPlayerCheck(player)) { - turn->type = POKER_TURN_TYPE_CHECK; - turn->chips = 0; - turn->confidence = 1000; - } else { - turn->type = POKER_TURN_TYPE_FOLD; - turn->chips = 0; - turn->confidence = 1000 - confidence; - } -} - -inline bool pokerCanPlayerCheck(uint8_t player) { - return ( - POKER_POTS[POKER_POT_CURRENT].players[player] == - POKER_POTS[POKER_POT_CURRENT].call - ); -} - -inline bool pokerDoesPlayerNeedToBet(uint8_t playerIndex) { - uint8_t i; - pokerplayer_t *player = POKER_PLAYERS + playerIndex; - - // Can this player even participate? - if( - (player->state & (POKER_PLAYER_STATE_FOLDED|POKER_PLAYER_STATE_OUT)) != 0 || - player->chips == 0 - ) { - return false; - } - - // Has the player bet? If so are they in the current pot? - if((player->state & POKER_PLAYER_STATE_HAS_BET_THIS_ROUND) == 0) { - return true; - } - - //TODO: Refer to pokerbet function, but basically I can't let the player - //bet if they have bet more money than the second richest player. - - // Check each pot, if the pot is inactive or the player is CALLED/CHECKED - for(i = 0; i < POKER_POT_COUNT_MAX; i++) { - // Is this pot active? - if(POKER_POTS[i].chips == 0) break; - - // Is the player called into this pot all the way? - if(POKER_POTS[i].players[playerIndex] == POKER_POTS[i].call) continue; - return true; - } - - return false; -} - -inline uint8_t pokerGetRemainingBetterCount() { - uint8_t i, count; - count = 0; - for(i = 0 ; i < POKER_PLAYER_COUNT; i++) { - if(pokerDoesPlayerNeedToBet(i)) count++; - } - return count; -} - -void pokerWinnerFillRemaining(pokerplayerwinning_t *winning) { - uint8_t i, highest, current, highestCard, currentCard; - - // Set the kicker - winning->kicker = 0xFF; - - // Fill the remaining cards - while(winning->setSize < POKER_WINNING_SET_SIZE) { - highest = 0xFF; - - for(i = 0; i < winning->fullSize; i++) { - currentCard = winning->full[i]; - if(cardContains(winning->set, winning->setSize, currentCard) != 0xFF) { - continue; - } - - if(highest == 0xFF) { - highestCard = currentCard; - highest = cardGetNumber(highestCard); - } else { - current = cardGetNumber(currentCard); - if(current != CARD_ACE && current < highest) continue; - highestCard = currentCard; - highest = current; - } - } - - if(highest == 0xFF) break; - winning->set[winning->setSize++] = highestCard; - } - // cardHandSort(winning->set, winning->setSize); -} - -void pokerWinnerGetForPlayer(uint8_t playerIndex,pokerplayerwinning_t *winning){ - uint8_t i, j, l, card, number, suit, pairCount; - uint8_t index; - uint8_t pairs[CARD_SUIT_COUNT]; - pokerplayer_t *player; - - player = POKER_PLAYERS + playerIndex; - - // Get the full poker hand (should be a 7 card hand, but MAY not be) - for(i = 0; i < POKER_COMMUNITY_SIZE; i++) { - winning->full[i] = POKER_COMMUNITY[i]; - } - for(i = 0; i < POKER_PLAYER_HAND_SIZE_MAX; i++) { - winning->full[i + POKER_COMMUNITY_SIZE] = player->hand[i]; - } - winning->fullSize = POKER_COMMUNITY_SIZE + POKER_PLAYER_HAND_SIZE_MAX; - - // TODO: Do I need to sort this? - // cardHandSort(winning->full, winning->fullSize); - - // Reset the winning status. - winning->setSize = 0; - - //////////////////////// Now look for the winning set //////////////////////// - - // Royal / Straight Flush - for(i = 0; i < winning->fullSize; i++) { - card = winning->full[i]; - number = cardGetNumber(card); - if(number < CARD_FIVE) continue; - - suit = cardGetSuit(card); - winning->setSize = 1; - - // Now look for the matching cards (Reverse order to order from A to 10) - for(j = 1; j <= 4; j++) { - l = number == CARD_FIVE && j == 4 ? CARD_ACE : number - j;//Ace low. - index = cardContains(winning->full, winning->fullSize, cardGet(l, suit)); - if(index == 0xFF) break; - winning->set[j] = winning->full[index]; - winning->setSize++; - } - - // Check if has all necessary cards. - if(winning->setSize < POKER_WINNING_SET_SIZE) continue; - - // Add self to array - winning->set[0] = winning->full[i]; - winning->type = ( - number == CARD_ACE ? POKER_WINNING_TYPE_ROYAL_FLUSH : - POKER_WINNING_TYPE_STRAIGHT_FLUSH - ); - pokerWinnerFillRemaining(winning); - return; - } - - // Four of a kind. - winning->setSize = 0; - for(i = 0; i < winning->fullSize; i++) { - card = winning->full[i]; - number = cardGetNumber(card); - pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); - if(pairCount < CARD_SUIT_COUNT) continue; - - winning->setSize = pairCount; - for(j = 0; j < pairCount; j++) winning->set[j] = winning->full[pairs[j]]; - winning->type = POKER_WINNING_TYPE_FOUR_OF_A_KIND; - pokerWinnerFillRemaining(winning); - return; - } - - // Full House - winning->setSize = 0; - for(i = 0; i < winning->fullSize; i++) { - // Check we haven't already added this card. - card = winning->full[i]; - if(cardContains(winning->set, winning->setSize, card) != 0xFF) continue; - - number = cardGetNumber(card); - pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); - - // Did we find either two pair or three pair? - if(pairCount != 2 && pairCount != 3) continue; - if(winning->setSize == 3) pairCount = 2;//Clamp to 5 max. - - // Copy found pairs. - for(j = 0; j < pairCount; j++) { - winning->set[winning->setSize + j] = winning->full[pairs[j]]; - } - winning->setSize += pairCount; - - // Winned? - if(winning->setSize != POKER_WINNING_SET_SIZE) continue; - winning->type = POKER_WINNING_TYPE_FULL_HOUSE; - pokerWinnerFillRemaining(winning); - return; - } - - // Flush (5 same suit) - for(i = 0; i < winning->fullSize; i++) { - card = winning->full[i]; - suit = cardGetSuit(card); - winning->setSize = 1; - for(j = i+1; j < winning->fullSize; j++) { - if(cardGetSuit(winning->full[j]) != suit) continue; - winning->set[winning->setSize++] = winning->full[j]; - if(winning->setSize == POKER_WINNING_SET_SIZE) break; - } - if(winning->setSize < POKER_WINNING_SET_SIZE) continue; - winning->set[0] = winning->full[i]; - winning->type = POKER_WINNING_TYPE_FLUSH; - pokerWinnerFillRemaining(winning); - return; - } - - // Straight (sequence any suit) - winning->setSize = 0; - for(i = 0; i < winning->fullSize; i++) { - card = winning->full[i]; - number = cardGetNumber(card); - if(number < CARD_FIVE) continue; - winning->setSize = 1; - - for(j = 1; j <= 4; j++) { - l = number == CARD_FIVE && j == 4 ? CARD_ACE : number - j;//Ace low. - index = cardContainsNumber(winning->full, winning->fullSize, l); - if(index == 0xFF) break; - winning->set[j] = winning->full[index]; - winning->setSize++; - } - - // Check if has all necessary cards. - if(winning->setSize < POKER_WINNING_SET_SIZE) continue; - winning->set[0] = winning->full[i]; - winning->type = POKER_WINNING_TYPE_STRAIGHT; - pokerWinnerFillRemaining(winning); - return; - } - - // Three of a kind - winning->setSize = 0; - for(i = 0; i < winning->fullSize; i++) { - card = winning->full[i]; - number = cardGetNumber(card); - pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); - if(pairCount != 3) continue; - - winning->setSize = pairCount; - for(j = 0; j < pairCount; j++) winning->set[j] = winning->full[pairs[j]]; - winning->type = POKER_WINNING_TYPE_THREE_OF_A_KIND; - pokerWinnerFillRemaining(winning); - return; - } - - // Two Pair - winning->setSize = 0; - for(i = 0; i < winning->fullSize; i++) { - card = winning->full[i];// Check we haven't already added this card. - if(cardContains(winning->set, winning->setSize, card) != 0xFF) { - continue; - } - - number = cardGetNumber(card); - pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); - if(pairCount != 2) continue; - - for(j = 0; j < pairCount; j++) { - winning->set[winning->setSize++] = winning->full[pairs[j]]; - } - if(winning->setSize != 4) continue; - - winning->type = POKER_WINNING_TYPE_TWO_PAIR; - pokerWinnerFillRemaining(winning); - return; - } - - // Pair - if(winning->setSize == 2) { - winning->type = POKER_WINNING_TYPE_PAIR; - pokerWinnerFillRemaining(winning); - return; - } - - // High card - winning->setSize = 0; - pokerWinnerFillRemaining(winning); - winning->type = POKER_WINNING_TYPE_HIGH_CARD; - - return; -} - -inline uint16_t pokerWinnerGetTypeConfidence(uint8_t type) { - switch(type) { - case POKER_WINNING_TYPE_ROYAL_FLUSH: - return POKER_WINNING_CONFIDENCE_ROYAL_FLUSH; - case POKER_WINNING_TYPE_STRAIGHT_FLUSH: - return POKER_WINNING_CONFIDENCE_STRAIGHT_FLUSH; - case POKER_WINNING_TYPE_FOUR_OF_A_KIND: - return POKER_WINNING_CONFIDENCE_FOUR_OF_A_KIND; - case POKER_WINNING_TYPE_FULL_HOUSE: - return POKER_WINNING_CONFIDENCE_FULL_HOUSE; - case POKER_WINNING_TYPE_FLUSH: - return POKER_WINNING_CONFIDENCE_FLUSH; - case POKER_WINNING_TYPE_STRAIGHT: - return POKER_WINNING_CONFIDENCE_STRAIGHT; - case POKER_WINNING_TYPE_THREE_OF_A_KIND: - return POKER_WINNING_CONFIDENCE_THREE_OF_A_KIND; - case POKER_WINNING_TYPE_TWO_PAIR: - return POKER_WINNING_CONFIDENCE_TWO_PAIR; - case POKER_WINNING_TYPE_PAIR: - return POKER_WINNING_CONFIDENCE_PAIR; - default: - return POKER_WINNING_CONFIDENCE_HIGH_CARD; - } -} - -uint8_t pokerWinnerCompare( - pokerplayerwinning_t *left, pokerplayerwinning_t *right -) { - uint8_t - i, number, card, countCardsSame, index, - highCardLeft, highCardRight, highNumberLeft, highNumberRight - ; - - highNumberLeft = 0xFF; - highNumberRight = 0xFF; - countCardsSame = 0; - - for(i = 0; i < left->setSize; i++) { - card = left->set[i]; - number = cardGetNumber(card); - if(highNumberLeft != 0xFF && number < highNumberLeft) continue;//Quick check - - // Check if this number is within the other hand or not - index = cardContainsNumber(right->set, right->setSize, number); - if(index == 0xFF) { - // This number IS within the other hand, let's check that the EXACT card - // is a match/isn't a match. - index = cardContains(right->set, right->setSize, card); - - // Exact card match - if(index != 0xFF) { - countCardsSame++; - continue; - } - // Not exact card match.. ? - } - - if(highNumberLeft == 0xFF||number == CARD_ACE||highNumberLeft < number) { - highNumberLeft = number; - highCardLeft = card; - } - } - - for(i = 0; i < right->setSize; i++) { - card = right->set[i]; - number = cardGetNumber(card); - if(highNumberRight != 0xFF && number < highNumberRight) continue; - - index = cardContainsNumber(left->set, left->setSize, number); - if(index != 0xFF) { - index = cardContains(left->set, left->setSize, card); - if(index != 0xFF) continue; - } - - if(highNumberRight == 0xFF||number == CARD_ACE||highNumberRight < number) { - highNumberRight = number; - highCardRight = card; - } - } - - - if(countCardsSame == left->setSize) { - for(i = 0; i < left->setSize; i++) { - card = left->set[i]; - number = cardGetNumber(card); - if(highNumberLeft == 0xFF||number == CARD_ACE||highNumberLeft < number) { - highNumberLeft = number; - highCardLeft = card; - } - } - return highCardLeft; - } - - if(highCardLeft == 0xFF) return 0xFF; - if(highNumberLeft < highNumberRight) return 0xFF; - return highCardLeft;//Greater or Equal to. -} - -void pokerWinnerDetermineForPot( - pokerpot_t *pot, - pokerplayerwinning_t *winners, - uint8_t *winnerPlayers, - uint8_t *winnerCount, - uint8_t *participants, - uint8_t *participantCount -) { - uint8_t i, j, countPlayers, countWinners, number, highNumber, card, highCard; - pokerplayerwinning_t *left, *right; - pokerplayer_t *player; - bool isWinner; - - countPlayers = 0; - countWinners = 0; - highCard = 0xFF; - - // Get participating players and their hands. - for(i = 0; i < POKER_PLAYER_COUNT; i++) { - if(pot->players[i] == 0) continue; - - player = POKER_PLAYERS + i; - if(player->state & (POKER_PLAYER_STATE_FOLDED|POKER_PLAYER_STATE_OUT)) { - continue; - } - - participants[countPlayers] = i; - pokerWinnerGetForPlayer(i, winners + countPlayers++); - } - - // Compare participating players - for(i = 0; i < countPlayers; i++) { - left = winners + i; - isWinner = true; - highNumber = 0xFF; - - for(j = 0; j < countPlayers; j++) { - if(i == j) continue; - right = winners + j; - - // Am I the better hand / Is it the better hand? - if(left->type < right->type) continue; - if(left->type > right->type) { - isWinner = false; - break; - } - - // Equal, compare hands. - card = pokerWinnerCompare(left, right); - if(card == 0xFF) { - isWinner = false; - break; - } - - // Determine high card. - number = cardGetNumber(card); - if(highNumber == 0xFF || number == CARD_ACE || number > highNumber) { - highCard = card; - highNumber = number; - } - } - - if(!isWinner) continue; - left->kicker = highCard; - winnerPlayers[countWinners++] = participants[i]; - } - - *participantCount = countPlayers; - *winnerCount = countWinners; +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "poker.h" + +pokerplayer_t POKER_PLAYERS[POKER_PLAYER_COUNT_MAX]; + +uint8_t POKER_DECK[CARD_DECK_SIZE]; +uint8_t POKER_DECK_SIZE; +uint8_t POKER_COMMUNITY[POKER_COMMUNITY_SIZE_MAX]; +uint8_t POKER_COMMUNITY_SIZE; + +uint8_t POKER_PLAYER_DEALER; +uint8_t POKER_PLAYER_SMALL_BLIND; +uint8_t POKER_PLAYER_BIG_BLIND; +uint8_t POKER_PLAYER_BETTER; + +uint16_t POKER_GAME_BLINDS_CURRENT; + +pokerpot_t POKER_POTS[POKER_POT_COUNT_MAX]; +uint8_t POKER_POT_CURRENT; +uint8_t POKER_POT_COUNT; + +pokerplayerwinning_t POKER_WINNERS[POKER_PLAYER_COUNT_MAX]; +uint8_t POKER_WINNER_PLAYERS[POKER_PLAYER_COUNT_MAX]; +uint8_t POKER_WINNER_PARTICIPANTS[POKER_PLAYER_COUNT_MAX]; +uint8_t POKER_WINNER_COUNT; +uint8_t POKER_WINNER_PARTICIPANT_COUNT; +uint8_t POKER_PLAYER_COUNT; + +void pokerInit() { + uint8_t i; + + // Set up the initial state. + // TODO: Should this be randomized? + POKER_PLAYER_DEALER = 0; + POKER_GAME_BLINDS_CURRENT = 10; + POKER_PLAYER_COUNT = POKER_PLAYER_COUNT_MAX; + + // Set up players + for(i = 0; i < POKER_PLAYER_COUNT; i++) { + POKER_PLAYERS[i].chips = 10000; + POKER_PLAYERS[i].state = 0; + } + + // Reset the round state (For the first round) + pokerNewRound(); +} + +void pokerNewRound() { + uint8_t i, j, k; + uint8_t found; + pokerplayer_t *player; + + // Reset round state + POKER_COMMUNITY_SIZE = 0; + POKER_POT_COUNT = 0; + POKER_POT_CURRENT = 0; + + // Reset the pots. + for(i = 0; i < POKER_POT_COUNT_MAX; i++) { + POKER_POTS[i].chips = 0; + POKER_POTS[i].call = 0; + for(j = 0; j < POKER_PLAYER_COUNT; j++) { + POKER_POTS[i].players[j] = 0; + } + } + + // Fill deck + for(i = 0; i < CARD_DECK_SIZE; i++) POKER_DECK[i] = i; + POKER_DECK_SIZE = CARD_DECK_SIZE; + + // Shuffle Deck + for(i = POKER_DECK_SIZE-1; i > 0; i--) { + k = rand(); + j = k % (i+1); + k = POKER_DECK[i]; + POKER_DECK[i] = POKER_DECK[j]; + POKER_DECK[j] = k; + } + + // Reset Players and decide new blinds. + found = 0; + POKER_PLAYER_DEALER++; + + // Update players for the round. + for(i = 0; i < POKER_PLAYER_COUNT; i++) { + player = POKER_PLAYERS + i; + player->timesRaised = 0; + player->state &= ~( + POKER_PLAYER_STATE_FOLDED | + POKER_PLAYER_STATE_HAS_BET_THIS_ROUND + ); + + // Update out state. + if(player->chips == 0) player->state |= POKER_PLAYER_STATE_OUT; + + // Is the player out? + if((player->state & POKER_PLAYER_STATE_OUT) != 0) continue; + + // Have we found the dealer, small blind, and big blind players? + if(found != 3) { + k = (POKER_PLAYER_DEALER + i) % POKER_PLAYER_COUNT; + + if(found == 0) {// Have we found the dealer? + POKER_PLAYER_DEALER = i; + found++; + } else if(found == 1) {// Have we found the small blind? + POKER_PLAYER_SMALL_BLIND = i; + found++; + } else if(found == 2) {// Have we found the big blind? + POKER_PLAYER_BIG_BLIND = i; + found++; + } + } + + // Deal two cards to the player. + for(j = 0; j < POKER_PLAYER_HAND_SIZE_MAX; j++) { + player->hand[j] = POKER_DECK[--POKER_DECK_SIZE]; + } + } + + // Take blinds + // TODO: I need to make sure the blind players even have the chips to blind. + pokerBet(POKER_PLAYER_SMALL_BLIND, POKER_GAME_BLINDS_CURRENT); + pokerBet(POKER_PLAYER_BIG_BLIND, (POKER_GAME_BLINDS_CURRENT*2)); + + // Set the initial better, we set this to the BIG BLIND player because we will + // cycle to the "next better" as soon as the game starts. + POKER_PLAYER_BETTER = POKER_PLAYER_BIG_BLIND; +} + +inline void pokerBet(uint8_t player, uint16_t amount) { + // TODO: This may become a function because if a player doesn't have enough + // chips to bet to the active pot, then the pot needs to autosplit, take those + // who have bet into the pot up to the amount that the player betting can bet, + // and push them into a new pot. + // There also needs to be a limit on this, for example; + // player 0 has $1200, and bets $1000, they can't bet more than that ever. + // player 1 has $1000, and bets all of it. The remanin + // player 2 has $700, and bets all o it. A new $300 sidepot auto creates + // player 3 has $500 and bets all of it, Another sidepot with $200 is auto made. + if(POKER_POT_CURRENT == POKER_POT_COUNT) POKER_POT_COUNT++; + + POKER_PLAYERS[player].chips -= amount; + POKER_POTS[POKER_POT_CURRENT].chips += amount; + POKER_POTS[POKER_POT_CURRENT].players[player] += amount; + POKER_POTS[POKER_POT_CURRENT].call = MATH_MAX( + amount, POKER_POTS[POKER_POT_CURRENT].players[player] + ); +} + +inline uint8_t pokerGetCallBet(uint8_t player) { + return ( + POKER_POTS[POKER_POT_CURRENT].call - + POKER_POTS[POKER_POT_CURRENT].players[player] + ); +} + +void pokerAi(uint8_t player, pokerturn_t *turn) { + uint8_t i, cardNumber0, cardNumber1, suitNumber0, suitNumber1; + uint16_t callBet, maxBet, amount, bluffBet; + uint8_t random;// TODO: Determine type. + uint16_t confidence, expectedGain, potOdds; + pokerplayerwinning_t winning; + pokerplayer_t *plyr = POKER_PLAYERS + player; + + // The following logic is heavily inspired by; + // https://github.com/gorel/C-Poker-AI/blob/master/src/common/pokerai.c + // But with some changes and smarts added by me. The original source code will + // essentially just run a crap tun of simulated games and get the times that + // they are expected to win from those games, but I'm just going to use the + // odds of the hand being the winning hand. + + // Is this preflop? + if(POKER_COMMUNITY_SIZE == 0) { + // Get the hand weight + cardNumber0 = cardGetNumber(plyr->hand[0]); + cardNumber1 = cardGetNumber(plyr->hand[1]); + suitNumber0 = cardGetSuit(plyr->hand[0]); + suitNumber1 = cardGetSuit(plyr->hand[1]); + + // Get delta between cards + i = MATH_ABS(cardNumber0 - cardNumber1); + + // Get card weight + confidence = cardNumber0 + cardNumber1; + if(cardNumber0 == cardNumber1) {// Pairs + confidence += 6; + } else if(suitNumber0 == suitNumber1) {// Same suit + confidence += 4; + } + + // Get difference from cards for guessing flush + if(i > 4) { + confidence -= 4; + } else if(i > 2) { + confidence -= i; + } + + // Confidence is now a value between 0-30 (inclusive). Multiply by 1000 + confidence = (confidence * 1000) / 30; + // Now do over 30 to get the value represented as 0-1000 + } else { + // Simulate my hand being the winning hand, use that as the confidence + // TODO: Repurpose old code lmao. Just take it from Dawn-C + BGB_printf("Holy shit you guys"); + pokerWinnerGetForPlayer(player, &winning); + BGB_printf("Winning type %u", winning.type); + confidence = pokerWinnerGetTypeConfidence(winning.type); + BGB_printf("Confidence %u", confidence); + } + + // Now we know how confident the AI is, let's put a chip value to that weight + // How many chips to call? + callBet = pokerGetCallBet(player); + BGB_printf("Callbet %u", callBet); + + + // Do they need chips to call, or is it possible to check? + if(callBet > 0) { + // Work out how many chips the player could possibly win. This is a number + // between 0 and player count * initial chips, at the time of writing that + // is around 50,000. + expectedGain = 0; + for(i = 0; i < POKER_POT_COUNT; i++) { + if(POKER_POTS[i].players[player] == 0) break; + expectedGain += POKER_POTS[i].chips; + } + + + // Now work out the pot odds, this is basically "how many times the callbet + // could I win if I win this hand". e.g. Desirable hands will have an + // expected gain much higher than the callbet. + potOdds = plyr->chips / 1000; + potOdds = MATH_MAX((callBet + expectedGain), 1) / MATH_MAX(potOdds, 1); + + BGB_printf("Expected Gain and Pot Odds %u, %u", expectedGain, potOdds); + } else { + // If the call bet is zero then there's fairly equal odds, so let's just + // take the chances out of the remainig player count. + potOdds = 1000 / pokerGetRemainingBetterCount() * 2;// 0 - 1000 + BGB_printf("Pot Odds %u", potOdds); + } + + // Now determine the expected ROI + //TODO: I think these values are a bit odd. + expectedGain = (confidence*100) / (potOdds / 10); + BGB_printf("Expected Gains (2) %u", expectedGain); + + // Now get a random number 0-100. + random = rand(); + random = random % 100; + + // Determine the max bet that the AI is willing to make. The max bet is + // basically how high the AI is willing to bet. + maxBet = plyr->chips;// TODO: Replace with below code and test, just running + // With this for now. + // maxBet = plyr->chips / MATH_MAX(random / 10, 1); + // maxBet -= callBet; + // BGB_printf("Rand %u, Max Bet %u", random, callBet); + + // Determine what's a good bluff bet. + // TODO: not float + bluffBet = maxBet; + // bluffBet = ((random * 100) / maxBet) * plyr->chips; + + // Now prep the output + turn->bluff = false; + amount = 0; + + BGB_printf("Random Dice %u", random); + + + // Now the actual AI can happen. This is basically a weight to confidence + // ratio. The higher the gains and the confidence then the more likely the AI + // is to betting. There are also bluff chances within here. + if(expectedGain < 800 && confidence < 800) { + if(random < 95) { + amount = 0; + } else { + amount = bluffBet; + turn->bluff = true; + } + } else if ((expectedGain < 1000 && confidence < 850) || confidence < 100) { + if (random < 80) { + amount = 0; + } else if(random < 5) { + amount = callBet; + turn->bluff = true; + } else { + amount = bluffBet; + turn->bluff = true; + } + } else if ((expectedGain < 1300 && confidence < 900) || confidence < 500) { + if (random < 60 || confidence < 500) { + amount = callBet; + } else { + amount = maxBet; + } + } else if (confidence < 950 || POKER_COMMUNITY_SIZE < 0x04) { + if(random < 20) { + amount = callBet; + } else { + amount = maxBet; + } + } else { + // TODO: check this + amount = (plyr->chips - callBet) * 9 / 10; + } + + // If this is the first round... make it a lot less likely I'll bet + if(POKER_COMMUNITY_SIZE == 0x00 && amount > callBet) { + if(random > 5) amount = callBet; + } + + + // Did we actually bet? + if(amount > 0) { + // Let's not get caught in a raising loop with AI. + if(plyr->timesRaised >= POKER_TURN_MAX_RAISES) amount = callBet; + amount = MATH_MAX(amount, callBet); + amount = MATH_MIN(amount, plyr->chips); + turn->chips = amount; + turn->confidence = confidence; + + if(plyr->chips == amount) { + turn->type = POKER_TURN_TYPE_ALL_IN; + } else if(amount == callBet) { + turn->type = POKER_TURN_TYPE_CALL; + } else { + turn->type = POKER_TURN_TYPE_BET; + } + } else if(pokerCanPlayerCheck(player)) { + turn->type = POKER_TURN_TYPE_CHECK; + turn->chips = 0; + turn->confidence = 1000; + } else { + turn->type = POKER_TURN_TYPE_FOLD; + turn->chips = 0; + turn->confidence = 1000 - confidence; + } +} + +inline bool pokerCanPlayerCheck(uint8_t player) { + return ( + POKER_POTS[POKER_POT_CURRENT].players[player] == + POKER_POTS[POKER_POT_CURRENT].call + ); +} + +inline bool pokerDoesPlayerNeedToBet(uint8_t playerIndex) { + uint8_t i; + pokerplayer_t *player = POKER_PLAYERS + playerIndex; + + // Can this player even participate? + if( + (player->state & (POKER_PLAYER_STATE_FOLDED|POKER_PLAYER_STATE_OUT)) != 0 || + player->chips == 0 + ) { + return false; + } + + // Has the player bet? If so are they in the current pot? + if((player->state & POKER_PLAYER_STATE_HAS_BET_THIS_ROUND) == 0) { + return true; + } + + //TODO: Refer to pokerbet function, but basically I can't let the player + //bet if they have bet more money than the second richest player. + + // Check each pot, if the pot is inactive or the player is CALLED/CHECKED + for(i = 0; i < POKER_POT_COUNT_MAX; i++) { + // Is this pot active? + if(POKER_POTS[i].chips == 0) break; + + // Is the player called into this pot all the way? + if(POKER_POTS[i].players[playerIndex] == POKER_POTS[i].call) continue; + return true; + } + + return false; +} + +inline uint8_t pokerGetRemainingBetterCount() { + uint8_t i, count; + count = 0; + for(i = 0 ; i < POKER_PLAYER_COUNT; i++) { + if(pokerDoesPlayerNeedToBet(i)) count++; + } + return count; +} + +void pokerWinnerFillRemaining(pokerplayerwinning_t *winning) { + uint8_t i, highest, current, highestCard, currentCard; + + // Set the kicker + winning->kicker = 0xFF; + + // Fill the remaining cards + while(winning->setSize < POKER_WINNING_SET_SIZE) { + highest = 0xFF; + + for(i = 0; i < winning->fullSize; i++) { + currentCard = winning->full[i]; + if(cardContains(winning->set, winning->setSize, currentCard) != 0xFF) { + continue; + } + + if(highest == 0xFF) { + highestCard = currentCard; + highest = cardGetNumber(highestCard); + } else { + current = cardGetNumber(currentCard); + if(current != CARD_ACE && current < highest) continue; + highestCard = currentCard; + highest = current; + } + } + + if(highest == 0xFF) break; + winning->set[winning->setSize++] = highestCard; + } + // cardHandSort(winning->set, winning->setSize); +} + +void pokerWinnerGetForPlayer(uint8_t playerIndex,pokerplayerwinning_t *winning){ + uint8_t i, j, l, card, number, suit, pairCount; + uint8_t index; + uint8_t pairs[CARD_SUIT_COUNT]; + pokerplayer_t *player; + + player = POKER_PLAYERS + playerIndex; + + // Get the full poker hand (should be a 7 card hand, but MAY not be) + for(i = 0; i < POKER_COMMUNITY_SIZE; i++) { + winning->full[i] = POKER_COMMUNITY[i]; + } + for(i = 0; i < POKER_PLAYER_HAND_SIZE_MAX; i++) { + winning->full[i + POKER_COMMUNITY_SIZE] = player->hand[i]; + } + winning->fullSize = POKER_COMMUNITY_SIZE + POKER_PLAYER_HAND_SIZE_MAX; + + // TODO: Do I need to sort this? + // cardHandSort(winning->full, winning->fullSize); + + // Reset the winning status. + winning->setSize = 0; + + //////////////////////// Now look for the winning set //////////////////////// + + // Royal / Straight Flush + for(i = 0; i < winning->fullSize; i++) { + card = winning->full[i]; + number = cardGetNumber(card); + if(number < CARD_FIVE) continue; + + suit = cardGetSuit(card); + winning->setSize = 1; + + // Now look for the matching cards (Reverse order to order from A to 10) + for(j = 1; j <= 4; j++) { + l = number == CARD_FIVE && j == 4 ? CARD_ACE : number - j;//Ace low. + index = cardContains(winning->full, winning->fullSize, cardGet(l, suit)); + if(index == 0xFF) break; + winning->set[j] = winning->full[index]; + winning->setSize++; + } + + // Check if has all necessary cards. + if(winning->setSize < POKER_WINNING_SET_SIZE) continue; + + // Add self to array + winning->set[0] = winning->full[i]; + winning->type = ( + number == CARD_ACE ? POKER_WINNING_TYPE_ROYAL_FLUSH : + POKER_WINNING_TYPE_STRAIGHT_FLUSH + ); + pokerWinnerFillRemaining(winning); + return; + } + + // Four of a kind. + winning->setSize = 0; + for(i = 0; i < winning->fullSize; i++) { + card = winning->full[i]; + number = cardGetNumber(card); + pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); + if(pairCount < CARD_SUIT_COUNT) continue; + + winning->setSize = pairCount; + for(j = 0; j < pairCount; j++) winning->set[j] = winning->full[pairs[j]]; + winning->type = POKER_WINNING_TYPE_FOUR_OF_A_KIND; + pokerWinnerFillRemaining(winning); + return; + } + + // Full House + winning->setSize = 0; + for(i = 0; i < winning->fullSize; i++) { + // Check we haven't already added this card. + card = winning->full[i]; + if(cardContains(winning->set, winning->setSize, card) != 0xFF) continue; + + number = cardGetNumber(card); + pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); + + // Did we find either two pair or three pair? + if(pairCount != 2 && pairCount != 3) continue; + if(winning->setSize == 3) pairCount = 2;//Clamp to 5 max. + + // Copy found pairs. + for(j = 0; j < pairCount; j++) { + winning->set[winning->setSize + j] = winning->full[pairs[j]]; + } + winning->setSize += pairCount; + + // Winned? + if(winning->setSize != POKER_WINNING_SET_SIZE) continue; + winning->type = POKER_WINNING_TYPE_FULL_HOUSE; + pokerWinnerFillRemaining(winning); + return; + } + + // Flush (5 same suit) + for(i = 0; i < winning->fullSize; i++) { + card = winning->full[i]; + suit = cardGetSuit(card); + winning->setSize = 1; + for(j = i+1; j < winning->fullSize; j++) { + if(cardGetSuit(winning->full[j]) != suit) continue; + winning->set[winning->setSize++] = winning->full[j]; + if(winning->setSize == POKER_WINNING_SET_SIZE) break; + } + if(winning->setSize < POKER_WINNING_SET_SIZE) continue; + winning->set[0] = winning->full[i]; + winning->type = POKER_WINNING_TYPE_FLUSH; + pokerWinnerFillRemaining(winning); + return; + } + + // Straight (sequence any suit) + winning->setSize = 0; + for(i = 0; i < winning->fullSize; i++) { + card = winning->full[i]; + number = cardGetNumber(card); + if(number < CARD_FIVE) continue; + winning->setSize = 1; + + for(j = 1; j <= 4; j++) { + l = number == CARD_FIVE && j == 4 ? CARD_ACE : number - j;//Ace low. + index = cardContainsNumber(winning->full, winning->fullSize, l); + if(index == 0xFF) break; + winning->set[j] = winning->full[index]; + winning->setSize++; + } + + // Check if has all necessary cards. + if(winning->setSize < POKER_WINNING_SET_SIZE) continue; + winning->set[0] = winning->full[i]; + winning->type = POKER_WINNING_TYPE_STRAIGHT; + pokerWinnerFillRemaining(winning); + return; + } + + // Three of a kind + winning->setSize = 0; + for(i = 0; i < winning->fullSize; i++) { + card = winning->full[i]; + number = cardGetNumber(card); + pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); + if(pairCount != 3) continue; + + winning->setSize = pairCount; + for(j = 0; j < pairCount; j++) winning->set[j] = winning->full[pairs[j]]; + winning->type = POKER_WINNING_TYPE_THREE_OF_A_KIND; + pokerWinnerFillRemaining(winning); + return; + } + + // Two Pair + winning->setSize = 0; + for(i = 0; i < winning->fullSize; i++) { + card = winning->full[i];// Check we haven't already added this card. + if(cardContains(winning->set, winning->setSize, card) != 0xFF) { + continue; + } + + number = cardGetNumber(card); + pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); + if(pairCount != 2) continue; + + for(j = 0; j < pairCount; j++) { + winning->set[winning->setSize++] = winning->full[pairs[j]]; + } + if(winning->setSize != 4) continue; + + winning->type = POKER_WINNING_TYPE_TWO_PAIR; + pokerWinnerFillRemaining(winning); + return; + } + + // Pair + if(winning->setSize == 2) { + winning->type = POKER_WINNING_TYPE_PAIR; + pokerWinnerFillRemaining(winning); + return; + } + + // High card + winning->setSize = 0; + pokerWinnerFillRemaining(winning); + winning->type = POKER_WINNING_TYPE_HIGH_CARD; + + return; +} + +inline uint16_t pokerWinnerGetTypeConfidence(uint8_t type) { + switch(type) { + case POKER_WINNING_TYPE_ROYAL_FLUSH: + return POKER_WINNING_CONFIDENCE_ROYAL_FLUSH; + case POKER_WINNING_TYPE_STRAIGHT_FLUSH: + return POKER_WINNING_CONFIDENCE_STRAIGHT_FLUSH; + case POKER_WINNING_TYPE_FOUR_OF_A_KIND: + return POKER_WINNING_CONFIDENCE_FOUR_OF_A_KIND; + case POKER_WINNING_TYPE_FULL_HOUSE: + return POKER_WINNING_CONFIDENCE_FULL_HOUSE; + case POKER_WINNING_TYPE_FLUSH: + return POKER_WINNING_CONFIDENCE_FLUSH; + case POKER_WINNING_TYPE_STRAIGHT: + return POKER_WINNING_CONFIDENCE_STRAIGHT; + case POKER_WINNING_TYPE_THREE_OF_A_KIND: + return POKER_WINNING_CONFIDENCE_THREE_OF_A_KIND; + case POKER_WINNING_TYPE_TWO_PAIR: + return POKER_WINNING_CONFIDENCE_TWO_PAIR; + case POKER_WINNING_TYPE_PAIR: + return POKER_WINNING_CONFIDENCE_PAIR; + default: + return POKER_WINNING_CONFIDENCE_HIGH_CARD; + } +} + +uint8_t pokerWinnerCompare( + pokerplayerwinning_t *left, pokerplayerwinning_t *right +) { + uint8_t + i, number, card, countCardsSame, index, + highCardLeft, highCardRight, highNumberLeft, highNumberRight + ; + + highNumberLeft = 0xFF; + highNumberRight = 0xFF; + countCardsSame = 0; + + for(i = 0; i < left->setSize; i++) { + card = left->set[i]; + number = cardGetNumber(card); + if(highNumberLeft != 0xFF && number < highNumberLeft) continue;//Quick check + + // Check if this number is within the other hand or not + index = cardContainsNumber(right->set, right->setSize, number); + if(index == 0xFF) { + // This number IS within the other hand, let's check that the EXACT card + // is a match/isn't a match. + index = cardContains(right->set, right->setSize, card); + + // Exact card match + if(index != 0xFF) { + countCardsSame++; + continue; + } + // Not exact card match.. ? + } + + if(highNumberLeft == 0xFF||number == CARD_ACE||highNumberLeft < number) { + highNumberLeft = number; + highCardLeft = card; + } + } + + for(i = 0; i < right->setSize; i++) { + card = right->set[i]; + number = cardGetNumber(card); + if(highNumberRight != 0xFF && number < highNumberRight) continue; + + index = cardContainsNumber(left->set, left->setSize, number); + if(index != 0xFF) { + index = cardContains(left->set, left->setSize, card); + if(index != 0xFF) continue; + } + + if(highNumberRight == 0xFF||number == CARD_ACE||highNumberRight < number) { + highNumberRight = number; + highCardRight = card; + } + } + + + if(countCardsSame == left->setSize) { + for(i = 0; i < left->setSize; i++) { + card = left->set[i]; + number = cardGetNumber(card); + if(highNumberLeft == 0xFF||number == CARD_ACE||highNumberLeft < number) { + highNumberLeft = number; + highCardLeft = card; + } + } + return highCardLeft; + } + + if(highCardLeft == 0xFF) return 0xFF; + if(highNumberLeft < highNumberRight) return 0xFF; + return highCardLeft;//Greater or Equal to. +} + +void pokerWinnerDetermineForPot( + pokerpot_t *pot, + pokerplayerwinning_t *winners, + uint8_t *winnerPlayers, + uint8_t *winnerCount, + uint8_t *participants, + uint8_t *participantCount +) { + uint8_t i, j, countPlayers, countWinners, number, highNumber, card, highCard; + pokerplayerwinning_t *left, *right; + pokerplayer_t *player; + bool isWinner; + + countPlayers = 0; + countWinners = 0; + highCard = 0xFF; + + // Get participating players and their hands. + for(i = 0; i < POKER_PLAYER_COUNT; i++) { + if(pot->players[i] == 0) continue; + + player = POKER_PLAYERS + i; + if(player->state & (POKER_PLAYER_STATE_FOLDED|POKER_PLAYER_STATE_OUT)) { + continue; + } + + participants[countPlayers] = i; + pokerWinnerGetForPlayer(i, winners + countPlayers++); + } + + // Compare participating players + for(i = 0; i < countPlayers; i++) { + left = winners + i; + isWinner = true; + highNumber = 0xFF; + + for(j = 0; j < countPlayers; j++) { + if(i == j) continue; + right = winners + j; + + // Am I the better hand / Is it the better hand? + if(left->type < right->type) continue; + if(left->type > right->type) { + isWinner = false; + break; + } + + // Equal, compare hands. + card = pokerWinnerCompare(left, right); + if(card == 0xFF) { + isWinner = false; + break; + } + + // Determine high card. + number = cardGetNumber(card); + if(highNumber == 0xFF || number == CARD_ACE || number > highNumber) { + highCard = card; + highNumber = number; + } + } + + if(!isWinner) continue; + left->kicker = highCard; + winnerPlayers[countWinners++] = participants[i]; + } + + *participantCount = countPlayers; + *winnerCount = countWinners; } \ No newline at end of file diff --git a/src/poker/poker.h b/src/poker/poker.h index 5708eaf..ae5bfad 100644 --- a/src/poker/poker.h +++ b/src/poker/poker.h @@ -1,57 +1,57 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" -#include "../util.h" -#include "card.h" -#include "player.h" -#include "pot.h" -#include "turn.h" -#include "winner.h" - -#define POKER_COMMUNITY_SIZE_MAX 5 -#define POKER_HUMAN_INDEX 0x00 - -#define POKER_COUNT_FLOP 0x03 -#define POKER_COUNT_TURN 0x01 -#define POKER_COUNT_RIVER 0x01 - -extern uint8_t POKER_DECK[]; -extern uint8_t POKER_DECK_SIZE; -extern uint8_t POKER_COMMUNITY[]; -extern uint8_t POKER_COMMUNITY_SIZE; - -extern uint8_t POKER_PLAYER_DEALER; -extern uint8_t POKER_PLAYER_SMALL_BLIND; -extern uint8_t POKER_PLAYER_BIG_BLIND; -extern uint8_t POKER_PLAYER_BETTER; - -extern uint16_t POKER_GAME_BLINDS_CURRENT; - -void pokerInit(); -void pokerNewRound(); -inline void pokerBet(uint8_t player, uint16_t amount); -inline uint8_t pokerGetCallBet(uint8_t player); -void pokerAi(uint8_t player, pokerturn_t *turn); -inline bool pokerCanPlayerCheck(uint8_t player); -inline bool pokerDoesPlayerNeedToBet(uint8_t playerIndex); -inline uint8_t pokerGetRemainingBetterCount(); -void pokerWinnerFillRemaining(pokerplayerwinning_t *winning); -void pokerWinnerGetForPlayer(uint8_t playerIndex,pokerplayerwinning_t *winning); -inline uint16_t pokerWinnerGetTypeConfidence(uint8_t type); -uint8_t pokerWinnerCompare( - pokerplayerwinning_t *left, pokerplayerwinning_t *right -); -void pokerWinnerDetermineForPot( - pokerpot_t *pot, - pokerplayerwinning_t *winners, - uint8_t *winnerPlayers, - uint8_t *winnerCount, - uint8_t *participants, - uint8_t *participantCount +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "../util.h" +#include "card.h" +#include "player.h" +#include "pot.h" +#include "turn.h" +#include "winner.h" + +#define POKER_COMMUNITY_SIZE_MAX 5 +#define POKER_HUMAN_INDEX 0x00 + +#define POKER_COUNT_FLOP 0x03 +#define POKER_COUNT_TURN 0x01 +#define POKER_COUNT_RIVER 0x01 + +extern uint8_t POKER_DECK[]; +extern uint8_t POKER_DECK_SIZE; +extern uint8_t POKER_COMMUNITY[]; +extern uint8_t POKER_COMMUNITY_SIZE; + +extern uint8_t POKER_PLAYER_DEALER; +extern uint8_t POKER_PLAYER_SMALL_BLIND; +extern uint8_t POKER_PLAYER_BIG_BLIND; +extern uint8_t POKER_PLAYER_BETTER; + +extern uint16_t POKER_GAME_BLINDS_CURRENT; + +void pokerInit(); +void pokerNewRound(); +inline void pokerBet(uint8_t player, uint16_t amount); +inline uint8_t pokerGetCallBet(uint8_t player); +void pokerAi(uint8_t player, pokerturn_t *turn); +inline bool pokerCanPlayerCheck(uint8_t player); +inline bool pokerDoesPlayerNeedToBet(uint8_t playerIndex); +inline uint8_t pokerGetRemainingBetterCount(); +void pokerWinnerFillRemaining(pokerplayerwinning_t *winning); +void pokerWinnerGetForPlayer(uint8_t playerIndex,pokerplayerwinning_t *winning); +inline uint16_t pokerWinnerGetTypeConfidence(uint8_t type); +uint8_t pokerWinnerCompare( + pokerplayerwinning_t *left, pokerplayerwinning_t *right +); +void pokerWinnerDetermineForPot( + pokerpot_t *pot, + pokerplayerwinning_t *winners, + uint8_t *winnerPlayers, + uint8_t *winnerCount, + uint8_t *participants, + uint8_t *participantCount ); \ No newline at end of file diff --git a/src/poker/pot.h b/src/poker/pot.h index a0dfc3c..2857c3f 100644 --- a/src/poker/pot.h +++ b/src/poker/pot.h @@ -1,27 +1,27 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" -#include "player.h" - -#define POKER_POT_COUNT_MAX POKER_PLAYER_COUNT_MAX - -typedef struct { - /** Current pot of chips */ - uint16_t chips; - - /** Current call value for this pot */ - uint16_t call; - - /** Players who are participating in the pots current bet (in the pot) */ - uint16_t players[POKER_PLAYER_COUNT_MAX]; -} pokerpot_t; - -extern pokerpot_t POKER_POTS[]; -extern uint8_t POKER_POT_CURRENT; +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "player.h" + +#define POKER_POT_COUNT_MAX POKER_PLAYER_COUNT_MAX + +typedef struct { + /** Current pot of chips */ + uint16_t chips; + + /** Current call value for this pot */ + uint16_t call; + + /** Players who are participating in the pots current bet (in the pot) */ + uint16_t players[POKER_PLAYER_COUNT_MAX]; +} pokerpot_t; + +extern pokerpot_t POKER_POTS[]; +extern uint8_t POKER_POT_CURRENT; extern uint8_t POKER_POT_COUNT; \ No newline at end of file diff --git a/src/poker/turn.h b/src/poker/turn.h index d8c411f..a128815 100644 --- a/src/poker/turn.h +++ b/src/poker/turn.h @@ -1,30 +1,30 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" - -#define POKER_TURN_MAX_RAISES 0x02 - -/** Turn Types */ -#define POKER_TURN_TYPE_OUT 0x00 -#define POKER_TURN_TYPE_FOLD 0x01 -#define POKER_TURN_TYPE_BET 0x02 -#define POKER_TURN_TYPE_CALL 0x03 -#define POKER_TURN_TYPE_ALL_IN 0x04 -#define POKER_TURN_TYPE_CHECK 0x05 - -typedef struct { - /** What type of action the turn is */ - uint8_t type; - /** How many chips they did in their turn (if applicable) */ - uint16_t chips; - /** How confident the AI is about their turn. 0 = none, 1000 = full */ - uint16_t confidence; - /** Is this turn a bluff? */ - bool bluff; +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" + +#define POKER_TURN_MAX_RAISES 0x02 + +/** Turn Types */ +#define POKER_TURN_TYPE_OUT 0x00 +#define POKER_TURN_TYPE_FOLD 0x01 +#define POKER_TURN_TYPE_BET 0x02 +#define POKER_TURN_TYPE_CALL 0x03 +#define POKER_TURN_TYPE_ALL_IN 0x04 +#define POKER_TURN_TYPE_CHECK 0x05 + +typedef struct { + /** What type of action the turn is */ + uint8_t type; + /** How many chips they did in their turn (if applicable) */ + uint16_t chips; + /** How confident the AI is about their turn. 0 = none, 1000 = full */ + uint16_t confidence; + /** Is this turn a bluff? */ + bool bluff; } pokerturn_t; \ No newline at end of file diff --git a/src/poker/winner.h b/src/poker/winner.h index ff79c99..70af1e7 100644 --- a/src/poker/winner.h +++ b/src/poker/winner.h @@ -1,64 +1,64 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "../libs.h" -#include "card.h" - -/** Maximum number of cards a winning state can hold. */ -#define POKER_WINNING_FULL_SIZE 0x07 - -/** How many cards in the winning set */ -#define POKER_WINNING_SET_SIZE 0x05 - -/** Winning Types */ -#define POKER_WINNING_TYPE_NULL 0x00 -#define POKER_WINNING_TYPE_ROYAL_FLUSH 0x01 -#define POKER_WINNING_TYPE_STRAIGHT_FLUSH 0x02 -#define POKER_WINNING_TYPE_FOUR_OF_A_KIND 0x03 -#define POKER_WINNING_TYPE_FULL_HOUSE 0x04 -#define POKER_WINNING_TYPE_FLUSH 0x05 -#define POKER_WINNING_TYPE_STRAIGHT 0x06 -#define POKER_WINNING_TYPE_THREE_OF_A_KIND 0x07 -#define POKER_WINNING_TYPE_TWO_PAIR 0x08 -#define POKER_WINNING_TYPE_PAIR 0x09 -#define POKER_WINNING_TYPE_HIGH_CARD 0x0A - -/** Confidences of winning based on the current hand type */ -#define POKER_WINNING_CONFIDENCE_ROYAL_FLUSH 1000 -#define POKER_WINNING_CONFIDENCE_STRAIGHT_FLUSH 990 -#define POKER_WINNING_CONFIDENCE_FOUR_OF_A_KIND 900 -#define POKER_WINNING_CONFIDENCE_FULL_HOUSE 850 -#define POKER_WINNING_CONFIDENCE_FLUSH 800 -#define POKER_WINNING_CONFIDENCE_STRAIGHT 700 -#define POKER_WINNING_CONFIDENCE_THREE_OF_A_KIND 500 -#define POKER_WINNING_CONFIDENCE_TWO_PAIR 400 -#define POKER_WINNING_CONFIDENCE_PAIR 200 -#define POKER_WINNING_CONFIDENCE_HIGH_CARD 100 - -/** Holds information about a player's winning state */ -typedef struct { - /** The full set of both the dealer and player's hand */ - uint8_t full[POKER_WINNING_FULL_SIZE]; - uint8_t fullSize; - - /** Holds the winning set */ - uint8_t set[POKER_WINNING_SET_SIZE]; - uint8_t setSize; - - /** Winning Type */ - uint8_t type; - - /** If there was a kicker card it will be here, otherwise -1 for no kicker */ - uint8_t kicker; -} pokerplayerwinning_t; - -extern pokerplayerwinning_t POKER_WINNERS[POKER_PLAYER_COUNT_MAX]; -extern uint8_t POKER_WINNER_PLAYERS[POKER_PLAYER_COUNT_MAX]; -extern uint8_t POKER_WINNER_PARTICIPANTS[POKER_PLAYER_COUNT_MAX]; -extern uint8_t POKER_WINNER_COUNT; +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "card.h" + +/** Maximum number of cards a winning state can hold. */ +#define POKER_WINNING_FULL_SIZE 0x07 + +/** How many cards in the winning set */ +#define POKER_WINNING_SET_SIZE 0x05 + +/** Winning Types */ +#define POKER_WINNING_TYPE_NULL 0x00 +#define POKER_WINNING_TYPE_ROYAL_FLUSH 0x01 +#define POKER_WINNING_TYPE_STRAIGHT_FLUSH 0x02 +#define POKER_WINNING_TYPE_FOUR_OF_A_KIND 0x03 +#define POKER_WINNING_TYPE_FULL_HOUSE 0x04 +#define POKER_WINNING_TYPE_FLUSH 0x05 +#define POKER_WINNING_TYPE_STRAIGHT 0x06 +#define POKER_WINNING_TYPE_THREE_OF_A_KIND 0x07 +#define POKER_WINNING_TYPE_TWO_PAIR 0x08 +#define POKER_WINNING_TYPE_PAIR 0x09 +#define POKER_WINNING_TYPE_HIGH_CARD 0x0A + +/** Confidences of winning based on the current hand type */ +#define POKER_WINNING_CONFIDENCE_ROYAL_FLUSH 1000 +#define POKER_WINNING_CONFIDENCE_STRAIGHT_FLUSH 990 +#define POKER_WINNING_CONFIDENCE_FOUR_OF_A_KIND 900 +#define POKER_WINNING_CONFIDENCE_FULL_HOUSE 850 +#define POKER_WINNING_CONFIDENCE_FLUSH 800 +#define POKER_WINNING_CONFIDENCE_STRAIGHT 700 +#define POKER_WINNING_CONFIDENCE_THREE_OF_A_KIND 500 +#define POKER_WINNING_CONFIDENCE_TWO_PAIR 400 +#define POKER_WINNING_CONFIDENCE_PAIR 200 +#define POKER_WINNING_CONFIDENCE_HIGH_CARD 100 + +/** Holds information about a player's winning state */ +typedef struct { + /** The full set of both the dealer and player's hand */ + uint8_t full[POKER_WINNING_FULL_SIZE]; + uint8_t fullSize; + + /** Holds the winning set */ + uint8_t set[POKER_WINNING_SET_SIZE]; + uint8_t setSize; + + /** Winning Type */ + uint8_t type; + + /** If there was a kicker card it will be here, otherwise -1 for no kicker */ + uint8_t kicker; +} pokerplayerwinning_t; + +extern pokerplayerwinning_t POKER_WINNERS[POKER_PLAYER_COUNT_MAX]; +extern uint8_t POKER_WINNER_PLAYERS[POKER_PLAYER_COUNT_MAX]; +extern uint8_t POKER_WINNER_PARTICIPANTS[POKER_PLAYER_COUNT_MAX]; +extern uint8_t POKER_WINNER_COUNT; extern uint8_t POKER_WINNER_PARTICIPANT_COUNT; \ No newline at end of file diff --git a/src/sprites/spriteborder.c b/src/sprites/spriteborder.c new file mode 100644 index 0000000..a3913b6 --- /dev/null +++ b/src/sprites/spriteborder.c @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "spriteborder.h" + +inline void spriteBorderBuffer() { + spriteBuffer(SPRITE_BORDER_VRAM_START, BORDER_IMAGE_TILES, BORDER_IMAGE); +} + +inline void spriteBorderBufferEdges( + uint8_t *buffer, uint8_t bufferWidth, uint8_t bufferHeight, uint8_t fill +) { + uint8_t i, j, max; + max = bufferWidth * bufferHeight; + + // Corners + buffer[0] = SPRITE_BORDER_VRAM_START + SPRITE_BORDER_TOP_LEFT; + buffer[bufferWidth-1] = SPRITE_BORDER_VRAM_START + SPRITE_BORDER_TOP_RIGHT; + buffer[max-1] = SPRITE_BORDER_VRAM_START + SPRITE_BORDER_BOTTOM_RIGHT; + buffer[max-bufferWidth] = SPRITE_BORDER_VRAM_START + SPRITE_BORDER_BOTTOM_LEFT; + + // Edges + for(i = 1; i < bufferWidth-1; i++) { + buffer[i] = SPRITE_BORDER_VRAM_START + SPRITE_BORDER_BOTTOM_TOP; + buffer[max - 1 - i] = SPRITE_BORDER_VRAM_START + SPRITE_BORDER_BOTTOM_TOP; + } + for(i = 1; i < bufferHeight - 1; i++) { + buffer[bufferWidth * i] = SPRITE_BORDER_VRAM_START + SPRITE_BORDER_LEFT_RIGHT; + buffer[bufferWidth * (i+1) - 1] = SPRITE_BORDER_VRAM_START + SPRITE_BORDER_LEFT_RIGHT; + } + + // Inner + if(fill != 0xFF) { + for(j = 1; j < bufferHeight-1; j++) { + for(i = 1; i < bufferWidth-1; i++) { + buffer[i + (j * bufferWidth)] = fill; + } + } + } +} \ No newline at end of file diff --git a/src/sprites/spriteborder.h b/src/sprites/spriteborder.h new file mode 100644 index 0000000..7daa9d4 --- /dev/null +++ b/src/sprites/spriteborder.h @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "sprites.h" +#include "BORDER.h" +#include "spritefont.h" + +#define SPRITE_BORDER_VRAM_START SPRITE_FONT_VRAM_END +#define SPRITE_BORDER_VRAM_END SPRITE_BORDER_VRAM_START + BORDER_IMAGE_TILES + +#define SPRITE_BORDER_TOP_LEFT 0x00 +#define SPRITE_BORDER_TOP_RIGHT 0x01 +#define SPRITE_BORDER_LEFT_RIGHT 0x02 +#define SPRITE_BORDER_BOTTOM_LEFT 0x03 +#define SPRITE_BORDER_BOTTOM_RIGHT 0x04 +#define SPRITE_BORDER_BOTTOM_TOP 0x05 + +/** + * Buffer the border sprite into VRAM. + */ +inline void spriteBorderBuffer(); + +/** + * Buffer the edges of a border to a tile buffer. + * + * @param buffer Buffer to write the tiles in to. + * @param bufferWidth Width of the buffer that you want to write. + * @param bufferHeight Height of the buffer that you want to write. + * @param fill NULL for no fill, otherwise TILE INDEX of fill within border. + */ +inline void spriteBorderBufferEdges( + uint8_t *buffer, uint8_t bufferWidth, uint8_t bufferHeight, uint8_t fill +); \ No newline at end of file diff --git a/src/sprites/spritecards.c b/src/sprites/spritecards.c new file mode 100644 index 0000000..d41cac3 --- /dev/null +++ b/src/sprites/spritecards.c @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "spritecards.h" + +const uint8_t SPRITE_HEARTS_ACE[SPRITE_CARD_TILE_COUNT] = { + SPRITE_CARD_ACE_TOP, SPRITE_CARD_TOP, SPRITE_CARD_TOP, SPRITE_CARD_TOP_RIGHT, + SPRITE_CARD_LEFT, SPRITE_CARD_BLANK, SPRITE_CARD_BLANK, SPRITE_CARD_RIGHT, + SPRITE_CARD_LEFT, SPRITE_CARD_HEART_BIG_TOP_LEFT, SPRITE_CARD_HEART_BIG_TOP_RIGHT, SPRITE_CARD_RIGHT, + SPRITE_CARD_LEFT, SPRITE_CARD_HEART_BIG_BOTTOM_LEFT, SPRITE_CARD_HEART_BIG_BOTTOM_RIGHT, SPRITE_CARD_RIGHT, + SPRITE_CARD_LEFT, SPRITE_CARD_BLANK, SPRITE_CARD_BLANK, SPRITE_CARD_RIGHT, + SPRITE_CARD_BOTTOM_LEFT, SPRITE_CARD_BOTTOM, SPRITE_CARD_BOTTOM, SPRITE_CARD_BOTTOM_RIGHT +}; + +// const uint8_t *SPRITE_CARD_LIST[] = { +// SPRITE_HEARTS_ACE +// }; + +inline void spriteCardsBuffer() { + spriteBuffer(SPRITE_CARD_VRAM_START, CARDS_TILES_IMAGE_TILES, CARDS_TILES_IMAGE); +} + +inline uint8_t * spriteCardsForCard(uint8_t card) { + return SPRITE_HEARTS_ACE; +} + +inline void spriteCardBufferTiles(uint8_t *buffer, uint8_t card) { + uint8_t i; + uint8_t *spriteTiles = spriteCardsForCard(card); + while(i != SPRITE_CARD_TILE_COUNT) { + buffer[i] = spriteTiles[i]; + i++; + } +} \ No newline at end of file diff --git a/src/sprites/spritecards.h b/src/sprites/spritecards.h new file mode 100644 index 0000000..6a269ff --- /dev/null +++ b/src/sprites/spritecards.h @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "CARDS_TILES.h" +#include "../poker/card.h" +#include "spritetileset.h" +#include "sprites.h" +#include "spriteborder.h" + +#define SPRITE_CARD_VRAM_START SPRITE_BORDER_VRAM_END + +#define SPRITE_CARD_BLANK SPRITE_TILESET_WHITE + +#define SPRITE_CARD_ACE_TOP SPRITE_CARD_VRAM_START + 0 +#define SPRITE_CARD_ACE_BOTTOM SPRITE_CARD_ACE_TOP/* ref */ +#define SPRITE_CARD_TWO_TOP SPRITE_CARD_VRAM_START + 1 +#define SPRITE_CARD_TWO_BOTTOM SPRITE_CARD_TWO_TOP/* ref */ +#define SPRITE_CARD_THREE_TOP SPRITE_CARD_VRAM_START + 2 +#define SPRITE_CARD_THREE_BOTTOM SPRITE_CARD_THREE_TOP/* ref */ +#define SPRITE_CARD_FOUR_TOP SPRITE_CARD_VRAM_START + 3 +#define SPRITE_CARD_FOUR_BOTTOM SPRITE_CARD_THREE_TOP/* ref */ +#define SPRITE_CARD_FIVE_TOP SPRITE_CARD_VRAM_START + 4 +#define SPRITE_CARD_FIVE_BOTTOM SPRITE_CARD_FIVE_TOP/* ref */ +#define SPRITE_CARD_SIX_TOP SPRITE_CARD_VRAM_START + 5 +#define SPRITE_CARD_SIX_BOTTOM SPRITE_CARD_SIX_TOP/* ref */ +#define SPRITE_CARD_SEVEN_TOP SPRITE_CARD_VRAM_START + 6 +#define SPRITE_CARD_SEVEN_BOTTOM SPRITE_CARD_SEVEN_TOP/* ref */ +#define SPRITE_CARD_EIGHT_TOP SPRITE_CARD_VRAM_START + 7 +#define SPRITE_CARD_EIGHT_BOTTOM SPRITE_CARD_EIGHT_TOP/* ref */ +#define SPRITE_CARD_NINE_TOP SPRITE_CARD_VRAM_START + 8 +#define SPRITE_CARD_NINE_BOTTOM SPRITE_CARD_NINE_TOP/* ref */ +#define SPRITE_CARD_TEN_TOP SPRITE_CARD_VRAM_START + 9 +#define SPRITE_CARD_TEN_BOTTOM/* ref */ + +#define SPRITE_CARD_TOP SPRITE_CARD_VRAM_START + 11 +#define SPRITE_CARD_TOP_RIGHT SPRITE_CARD_VRAM_START + 10 +#define SPRITE_CARD_LEFT SPRITE_CARD_TOP/* ref */ +#define SPRITE_CARD_RIGHT SPRITE_CARD_TOP/* ref */ +#define SPRITE_CARD_BOTTOM_LEFT SPRITE_CARD_TOP_RIGHT/* ref */ +#define SPRITE_CARD_BOTTOM_RIGHT SPRITE_CARD_TOP_RIGHT/* ref */ +#define SPRITE_CARD_BOTTOM SPRITE_CARD_TOP/* ref */ + +#define SPRITE_CARD_DIAMOND_BIG_TOP_LEFT SPRITE_CARD_VRAM_START + 12 +#define SPRITE_CARD_DIAMOND_BIG_TOP_RIGHT SPRITE_CARD_DIAMOND_BIG_TOP_LEFT/* ref */ +#define SPRITE_CARD_DIAMOND_BIG_BOTTOM_LEFT SPRITE_CARD_DIAMOND_BIG_TOP_LEFT/* ref */ +#define SPRITE_CARD_DIAMOND_BIG_BOTTOM_RIGHT SPRITE_CARD_DIAMOND_BIG_TOP_LEFT/* ref */ +#define SPRITE_CARD_HEART_BIG_TOP_LEFT SPRITE_CARD_VRAM_START + 13 +#define SPRITE_CARD_HEART_BIG_TOP_RIGHT SPRITE_CARD_HEART_BIG_TOP_LEFT/* ref */ +#define SPRITE_CARD_HEART_BIG_BOTTOM_LEFT SPRITE_CARD_DIAMOND_BIG_BOTTOM_LEFT +#define SPRITE_CARD_HEART_BIG_BOTTOM_RIGHT SPRITE_CARD_DIAMOND_BIG_BOTTOM_RIGHT + +#define SPRITE_CARD_WIDTH 4 +#define SPRITE_CARD_HEIGHT 6 +#define SPRITE_CARD_TILE_COUNT (SPRITE_CARD_WIDTH * SPRITE_CARD_HEIGHT) + +extern const uint8_t SPRITE_HEARTS_ACE[]; + +inline void spriteCardsBuffer(); + +inline uint8_t * spriteCardsForCard(uint8_t card); + +inline void spriteCardBufferTiles(uint8_t *buffer, uint8_t card); \ No newline at end of file diff --git a/src/sprites/spritefont.c b/src/sprites/spritefont.c new file mode 100644 index 0000000..b2ccbe2 --- /dev/null +++ b/src/sprites/spritefont.c @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "spritefont.h" + +inline void spriteFontBuffer() { + spriteBuffer(SPRITE_FONT_VRAM_START, FONT_IMAGE_TILES, FONT_IMAGE); +} + +inline uint8_t spriteFontTileFromChar(char character) { + return character - SPRITE_FONT_FIRST_CHARACTER + SPRITE_FONT_VRAM_START; +} \ No newline at end of file diff --git a/src/sprites/spritefont.h b/src/sprites/spritefont.h new file mode 100644 index 0000000..0a4a69f --- /dev/null +++ b/src/sprites/spritefont.h @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "FONT.h" +#include "sprites.h" +#include "spritetileset.h" + +#define SPRITE_FONT_FIRST_CHARACTER 33 + +#define SPRITE_FONT_VRAM_START SPRITE_TILESET_VRAM_END +#define SPRITE_FONT_VRAM_END SPRITE_FONT_VRAM_START + FONT_IMAGE_TILES + +/** + * Buffer the font tiles to VRAM. + */ +inline void spriteFontBuffer(); + +/** + * Get the tile index for a given character (ASCII). + * + * @param character Character to get the tile index from. + * @return The tile index for the given character. + */ +inline uint8_t spriteFontTileFromChar(char character); \ No newline at end of file diff --git a/src/sprites/sprites.c b/src/sprites/sprites.c new file mode 100644 index 0000000..13834ba --- /dev/null +++ b/src/sprites/sprites.c @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "sprites.h" + +inline void spriteBuffer(uint8_t position, uint8_t length, uint8_t *tiles) { + set_bkg_data(position, length, tiles); +} \ No newline at end of file diff --git a/src/sprites/sprites.h b/src/sprites/sprites.h new file mode 100644 index 0000000..7247de8 --- /dev/null +++ b/src/sprites/sprites.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" + +/** + * Buffer tiles data into VRAM + * + * @param position Position in VRAM to buffer in to. + * @param length Length of the data being buffered (count of tiles). + * @param tiles Pointer to array of tiles to be bufffered. + */ +inline void spriteBuffer(uint8_t position, uint8_t length, uint8_t *tiles); \ No newline at end of file diff --git a/src/sprites/spritetileset.c b/src/sprites/spritetileset.c new file mode 100644 index 0000000..c42f74b --- /dev/null +++ b/src/sprites/spritetileset.c @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "spritetileset.h" + +void spriteTilesetBuffer() { + spriteBuffer(SPRITE_TILESET_VRAM_START, TILESET_IMAGE_TILES, TILESET_IMAGE); +} \ No newline at end of file diff --git a/src/sprites/spritetileset.h b/src/sprites/spritetileset.h new file mode 100644 index 0000000..ada36f1 --- /dev/null +++ b/src/sprites/spritetileset.h @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" +#include "TILESET.h" +#include "sprites.h" + +#define SPRITE_TILESET_VRAM_START 0x00 +#define SPRITE_TILESET_VRAM_END SPRITE_TILESET_VRAM_START + TILESET_IMAGE_TILES + +#define SPRITE_TILESET_TILE_0 SPRITE_TILESET_VRAM_START + 0x00 +#define SPRITE_TILESET_TILE_1 SPRITE_TILESET_VRAM_START + 0x01 +#define SPRITE_TILESET_TILE_2 SPRITE_TILESET_VRAM_START + 0x02 +#define SPRITE_TILESET_TILE_3 SPRITE_TILESET_VRAM_START + 0x03 + +#define SPRITE_TILESET_WHITE SPRITE_TILESET_TILE_0 +#define SPRITE_TILESET_BLACK SPRITE_TILESET_TILE_3 +#define SPRITE_TILESET_LIGHT SPRITE_TILESET_TILE_1 +#define SPRITE_TILESET_DARK SPRITE_TILESET_TILE_2 + + +// Shades are mapped where each set of 4 colors is mapped to two bits that will +// specify its darkness. Higher = Darker, Lower = Brighter +// 11 11 11 11 +#define TILESET_SHADE_BLACK 0xFF +// 11 11 11 10 +#define TILESET_SHADE_DARKER 0xFE +// 11 11 10 01 +#define TILESET_SHADE_DARK 0xF9 +// 11 10 01 00 +#define TILESET_SHADE_NORMAL 0xE4 +// 10 01 00 00 +#define TILESET_SHADE_BRIGHT 0x90 +// 01 00 00 00 +#define TILESET_SHADE_BRIGHTER 0x40 +// 00 00 00 00 +#define TILESET_SHADE_WHITE 0x00 + + +/** + * Buffer the TILESET tileset sprites onto VRAM. + * + * @param position Position in VRAM to buuffer on to. + */ +void spriteTilesetBuffer(); \ No newline at end of file diff --git a/src/strings.c b/src/strings.c index 52ebde9..6f34736 100644 --- a/src/strings.c +++ b/src/strings.c @@ -1,33 +1,33 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "strings.h" - -const char STR_ERROR[] = "An error\nhas occured"; -const char STR_HELLO[] = "Hello World, How are you today?\nGood thanks. Thank god!"; - -const char STR_POKER_GAME_START[] = "Poker game started"; -const char STR_POKER_GAME_TAKING_BLINDS[] = "Blinds taken."; -const char STR_POKER_GAME_CARDS_DEALT[] = "Cards dealt."; - -const char STR_POKER_GAME_CARDS_FLOPPED[] = "Cards flopped"; -const char STR_POKER_GAME_CARDS_TURNED[] = "Cards turned"; -const char STR_POKER_GAME_CARDS_RIVERED[] = "Cards river"; - -const char STR_DEBUG_WINNER_DECIDED[] = "DEBUG WINNER"; -const char STR_DEBUG_PLAYER[] = "DEBUG PLAYER"; - -const char STR_POKER_GAME_AI_FOLD[] = "AI Folding"; -const char STR_POKER_GAME_AI_RAISE[] = "AI Raise"; -const char STR_POKER_GAME_AI_RAISE_BLUFF[] = "AI Raise\nBut Bluffing"; -const char STR_POKER_GAME_AI_CALL[] = "AI Calling"; -const char STR_POKER_GAME_AI_CALL_BLUFF[] = "AI Calling\nBut Bluffing"; -const char STR_POKER_GAME_AI_ALL_IN[] = "AI All In"; -const char STR_POKER_GAME_AI_ALL_IN_BLUFF[] = "AI All In\nBut Bluffing"; -const char STR_POKER_GAME_AI_CHECK[] = "AI Checking"; -const char STR_POKER_GAME_AI_CHECK_BLUFF[] = "AI Checking\nBut Bluffing"; +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "strings.h" + +const char STR_ERROR[] = "An error\nhas occured"; +const char STR_HELLO[] = "Hello World, How are you today?\nGood thanks. Thank god!"; + +const char STR_POKER_GAME_START[] = "Poker game started"; +const char STR_POKER_GAME_TAKING_BLINDS[] = "Blinds taken."; +const char STR_POKER_GAME_CARDS_DEALT[] = "Cards dealt."; + +const char STR_POKER_GAME_CARDS_FLOPPED[] = "Cards flopped"; +const char STR_POKER_GAME_CARDS_TURNED[] = "Cards turned"; +const char STR_POKER_GAME_CARDS_RIVERED[] = "Cards river"; + +const char STR_DEBUG_WINNER_DECIDED[] = "DEBUG WINNER"; +const char STR_DEBUG_PLAYER[] = "DEBUG PLAYER"; + +const char STR_POKER_GAME_AI_FOLD[] = "AI Folding"; +const char STR_POKER_GAME_AI_RAISE[] = "AI Raise"; +const char STR_POKER_GAME_AI_RAISE_BLUFF[] = "AI Raise\nBut Bluffing"; +const char STR_POKER_GAME_AI_CALL[] = "AI Calling"; +const char STR_POKER_GAME_AI_CALL_BLUFF[] = "AI Calling\nBut Bluffing"; +const char STR_POKER_GAME_AI_ALL_IN[] = "AI All In"; +const char STR_POKER_GAME_AI_ALL_IN_BLUFF[] = "AI All In\nBut Bluffing"; +const char STR_POKER_GAME_AI_CHECK[] = "AI Checking"; +const char STR_POKER_GAME_AI_CHECK_BLUFF[] = "AI Checking\nBut Bluffing"; const char STR_FOX[] = "The quick brown fox jumps over the lazy dog."; \ No newline at end of file diff --git a/src/strings.h b/src/strings.h index 8fc0dee..5328d53 100644 --- a/src/strings.h +++ b/src/strings.h @@ -1,34 +1,34 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "libs.h" - -extern const char STR_ERROR[]; -extern const char STR_HELLO[]; - -extern const char STR_POKER_GAME_START[]; -extern const char STR_POKER_GAME_TAKING_BLINDS[]; -extern const char STR_POKER_GAME_CARDS_DEALT[]; - -extern const char STR_POKER_GAME_CARDS_FLOPPED[]; -extern const char STR_POKER_GAME_CARDS_TURNED[]; -extern const char STR_POKER_GAME_CARDS_RIVERED[]; - -extern const char STR_DEBUG_WINNER_DECIDED[]; -extern const char STR_DEBUG_PLAYER[]; - -extern const char STR_POKER_GAME_AI_FOLD[]; -extern const char STR_POKER_GAME_AI_RAISE[]; -extern const char STR_POKER_GAME_AI_RAISE_BLUFF[]; -extern const char STR_POKER_GAME_AI_CALL[]; -extern const char STR_POKER_GAME_AI_CALL_BLUFF[]; -extern const char STR_POKER_GAME_AI_ALL_IN[]; -extern const char STR_POKER_GAME_AI_ALL_IN_BLUFF[]; -extern const char STR_POKER_GAME_AI_CHECK[]; -extern const char STR_POKER_GAME_AI_CHECK_BLUFF[]; +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "libs.h" + +extern const char STR_ERROR[]; +extern const char STR_HELLO[]; + +extern const char STR_POKER_GAME_START[]; +extern const char STR_POKER_GAME_TAKING_BLINDS[]; +extern const char STR_POKER_GAME_CARDS_DEALT[]; + +extern const char STR_POKER_GAME_CARDS_FLOPPED[]; +extern const char STR_POKER_GAME_CARDS_TURNED[]; +extern const char STR_POKER_GAME_CARDS_RIVERED[]; + +extern const char STR_DEBUG_WINNER_DECIDED[]; +extern const char STR_DEBUG_PLAYER[]; + +extern const char STR_POKER_GAME_AI_FOLD[]; +extern const char STR_POKER_GAME_AI_RAISE[]; +extern const char STR_POKER_GAME_AI_RAISE_BLUFF[]; +extern const char STR_POKER_GAME_AI_CALL[]; +extern const char STR_POKER_GAME_AI_CALL_BLUFF[]; +extern const char STR_POKER_GAME_AI_ALL_IN[]; +extern const char STR_POKER_GAME_AI_ALL_IN_BLUFF[]; +extern const char STR_POKER_GAME_AI_CHECK[]; +extern const char STR_POKER_GAME_AI_CHECK_BLUFF[]; extern const char STR_FOX[]; \ No newline at end of file diff --git a/src/time.c b/src/time.c index c2efaf6..af1f38e 100644 --- a/src/time.c +++ b/src/time.c @@ -1,22 +1,22 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "time.h" - -uint16_t TIME_CURRENT; -uint16_t TIME_FUTURE; -uint8_t TIME_FUTURE_TYPE; - -inline void timeInit() { - TIME_CURRENT = 0; - TIME_FUTURE = 0; - TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_NULL; -} - -inline void timeUpdate() { - TIME_CURRENT++; +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "time.h" + +uint16_t TIME_CURRENT; +uint16_t TIME_FUTURE; +uint8_t TIME_FUTURE_TYPE; + +inline void timeInit() { + TIME_CURRENT = 0; + TIME_FUTURE = 0; + TIME_FUTURE_TYPE = TIME_FUTURE_TYPE_NULL; +} + +inline void timeUpdate() { + TIME_CURRENT++; } \ No newline at end of file diff --git a/src/time.h b/src/time.h index fe79f87..db0ff7f 100644 --- a/src/time.h +++ b/src/time.h @@ -1,25 +1,25 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "libs.h" - -#define TIME_PER_SECOND 60 - -#define TIME_FUTURE_TYPE_NULL 0x00 -#define TIME_FUTURE_TYPE_PAUSE 0x01 -#define TIME_FUTURE_TYPE_FADE_TO_BLACK 0x02 -#define TIME_FUTURE_TYPE_FADE_FROM_BLACK 0x03 -#define TIME_FUTURE_TYPE_FADE_TO_WHITE 0x04 -#define TIME_FUTURE_TYPE_FADE_FROM_WHITE 0x05 - -extern uint16_t TIME_CURRENT; -extern uint16_t TIME_FUTURE; -extern uint8_t TIME_FUTURE_TYPE; - -inline void timeInit(); +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "libs.h" + +#define TIME_PER_SECOND 60 + +#define TIME_FUTURE_TYPE_NULL 0x00 +#define TIME_FUTURE_TYPE_PAUSE 0x01 +#define TIME_FUTURE_TYPE_FADE_TO_BLACK 0x02 +#define TIME_FUTURE_TYPE_FADE_FROM_BLACK 0x03 +#define TIME_FUTURE_TYPE_FADE_TO_WHITE 0x04 +#define TIME_FUTURE_TYPE_FADE_FROM_WHITE 0x05 + +extern uint16_t TIME_CURRENT; +extern uint16_t TIME_FUTURE; +extern uint8_t TIME_FUTURE_TYPE; + +inline void timeInit(); inline void timeUpdate(); \ No newline at end of file diff --git a/src/util.h b/src/util.h index 0c27a92..d56e1c8 100644 --- a/src/util.h +++ b/src/util.h @@ -1,13 +1,13 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "libs.h" - -#define MATH_MIN(a, b) a > b ? b : a -#define MATH_MAX(a, b) a < b ? b : a +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "libs.h" + +#define MATH_MIN(a, b) a > b ? b : a +#define MATH_MAX(a, b) a < b ? b : a #define MATH_ABS(n) (n < 0 ? -n : n) \ No newline at end of file diff --git a/test.sh b/test.sh index 94352a0..80fc2e4 100644 --- a/test.sh +++ b/test.sh @@ -1,8 +1,8 @@ -#!/bin/bash -# Send over latest build -scp ./build/Penny.gb root@ywbud3:/storage/roms/gb/Penny.gb - -systemctl stop emustation.service -killall emulationstation -retroarch -L /lib/libretro/gambatte_libretro.so "/storage/roms/gb/Penny.gb" +#!/bin/bash +# Send over latest build +scp ./build/Penny.gb root@ywbud3:/storage/roms/gb/Penny.gb + +systemctl stop emustation.service +killall emulationstation +retroarch -L /lib/libretro/gambatte_libretro.so "/storage/roms/gb/Penny.gb" systemctl start emustation.service \ No newline at end of file