From 2885e77258bf31fce829f3557c1ded6d092fbabe Mon Sep 17 00:00:00 2001 From: jl777 Date: Wed, 6 Mar 2019 23:06:59 -1100 Subject: [PATCH] Test --- src/cc/tetris.cpp | 2393 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2393 insertions(+) create mode 100644 src/cc/tetris.cpp diff --git a/src/cc/tetris.cpp b/src/cc/tetris.cpp new file mode 100644 index 000000000..2b1c7736c --- /dev/null +++ b/src/cc/tetris.cpp @@ -0,0 +1,2393 @@ + +/***************************************************************************/ +/** https://github.com/brenns10/tetris + @file main.c + @author Stephen Brennan + @date Created Wednesday, 10 June 2015 + @brief Main program for tetris. + @copyright Copyright (c) 2015, Stephen Brennan. Released under the Revised + BSD License. See LICENSE.txt for details. + *******************************************************************************/ + + +#ifndef TETRIS_H +#define TETRIS_H + +#include // for FILE +#include // for bool +#include +#include +#include +#include +#include + +#include +#include + +//#include +//#include + + +/* + Convert a tetromino type to its corresponding cell. + */ +#define TYPE_TO_CELL(x) ((x)+1) + +/* + Strings for how you would print a tetris board. + */ +#define TC_EMPTY_STR " " +#define TC_BLOCK_STR "\u2588" + +/* + Questions about a tetris cell. + */ +#define TC_IS_EMPTY(x) ((x) == TC_EMPTY) +#define TC_IS_FILLED(x) (!TC_IS_EMPTY(x)) + +/* + How many cells in a tetromino? + */ +#define TETRIS 4 +/* + How many tetrominos? + */ +#define NUM_TETROMINOS 7 +/* + How many orientations of a tetromino? + */ +#define NUM_ORIENTATIONS 4 + +/* + Level constants. + */ +#define MAX_LEVEL 19 +#define LINES_PER_LEVEL 10 + +/* + A "cell" is a 1x1 block within a tetris board. + */ +typedef enum { + TC_EMPTY, TC_CELLI, TC_CELLJ, TC_CELLL, TC_CELLO, TC_CELLS, TC_CELLT, TC_CELLZ +} tetris_cell; + +/* + A "type" is a type/shape of a tetromino. Not including orientation. + */ +typedef enum { + TET_I, TET_J, TET_L, TET_O, TET_S, TET_T, TET_Z +} tetris_type; + +/* + A row,column pair. Negative numbers allowed, because we need them for + offsets. + */ +typedef struct { + int row; + int col; +} tetris_location; + +/* + A "block" is a struct that contains information about a tetromino. + Specifically, what type it is, what orientation it has, and where it is. + */ +typedef struct { + int typ; + int ori; + tetris_location loc; +} tetris_block; + +/* + All possible moves to give as input to the game. + */ +typedef enum { + TM_LEFT, TM_RIGHT, TM_CLOCK, TM_COUNTER, TM_DROP, TM_HOLD, TM_NONE +} tetris_move; + +/* + A game object! + */ +typedef struct { + /* + Game board stuff: + */ + int rows; + int cols; + char *board; + /* + Scoring information: + */ + int points; + int level; + /* + Falling block is the one currently going down. Next block is the one that + will be falling after this one. Stored is the block that you can swap out. + */ + tetris_block falling; + tetris_block next; + tetris_block stored; + /* + Number of game ticks until the block will move down. + */ + int ticks_till_gravity; + /* + Number of lines until you advance to the next level. + */ + int lines_remaining; +} tetris_game; + +/* + This array stores all necessary information about the cells that are filled by + each tetromino. The first index is the type of the tetromino (i.e. shape, + e.g. I, J, Z, etc.). The next index is the orientation (0-3). The final + array contains 4 tetris_location objects, each mapping to an offset from a + point on the upper left that is the tetromino "origin". + */ +extern tetris_location TETROMINOS[NUM_TETROMINOS][NUM_ORIENTATIONS][TETRIS]; + +/* + This array tells you how many ticks per gravity by level. Decreases as level + increases, to add difficulty. + */ +extern int GRAVITY_LEVEL[MAX_LEVEL+1]; + +// Data structure manipulation. +void tg_init(tetris_game *obj, int rows, int cols); +tetris_game *tg_create(int rows, int cols); +void tg_destroy(tetris_game *obj); +void tg_delete(tetris_game *obj); +tetris_game *tg_load(FILE *f); +void tg_save(tetris_game *obj, FILE *f); + +// Public methods not related to memory: +char tg_get(tetris_game *obj, int row, int col); +bool tg_check(tetris_game *obj, int row, int col); +bool tg_tick(tetris_game *obj, tetris_move move); +void tg_print(tetris_game *obj, FILE *f); + +#endif // TETRIS_H + + +#define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) +#define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) + +/******************************************************************************* + Array Definitions + *******************************************************************************/ + +tetris_location TETROMINOS[NUM_TETROMINOS][NUM_ORIENTATIONS][TETRIS] = { + // I + {{{1, 0}, {1, 1}, {1, 2}, {1, 3}}, + {{0, 2}, {1, 2}, {2, 2}, {3, 2}}, + {{3, 0}, {3, 1}, {3, 2}, {3, 3}}, + {{0, 1}, {1, 1}, {2, 1}, {3, 1}}}, + // J + {{{0, 0}, {1, 0}, {1, 1}, {1, 2}}, + {{0, 1}, {0, 2}, {1, 1}, {2, 1}}, + {{1, 0}, {1, 1}, {1, 2}, {2, 2}}, + {{0, 1}, {1, 1}, {2, 0}, {2, 1}}}, + // L + {{{0, 2}, {1, 0}, {1, 1}, {1, 2}}, + {{0, 1}, {1, 1}, {2, 1}, {2, 2}}, + {{1, 0}, {1, 1}, {1, 2}, {2, 0}}, + {{0, 0}, {0, 1}, {1, 1}, {2, 1}}}, + // O + {{{0, 1}, {0, 2}, {1, 1}, {1, 2}}, + {{0, 1}, {0, 2}, {1, 1}, {1, 2}}, + {{0, 1}, {0, 2}, {1, 1}, {1, 2}}, + {{0, 1}, {0, 2}, {1, 1}, {1, 2}}}, + // S + {{{0, 1}, {0, 2}, {1, 0}, {1, 1}}, + {{0, 1}, {1, 1}, {1, 2}, {2, 2}}, + {{1, 1}, {1, 2}, {2, 0}, {2, 1}}, + {{0, 0}, {1, 0}, {1, 1}, {2, 1}}}, + // T + {{{0, 1}, {1, 0}, {1, 1}, {1, 2}}, + {{0, 1}, {1, 1}, {1, 2}, {2, 1}}, + {{1, 0}, {1, 1}, {1, 2}, {2, 1}}, + {{0, 1}, {1, 0}, {1, 1}, {2, 1}}}, + // Z + {{{0, 0}, {0, 1}, {1, 1}, {1, 2}}, + {{0, 2}, {1, 1}, {1, 2}, {2, 1}}, + {{1, 0}, {1, 1}, {2, 1}, {2, 2}}, + {{0, 1}, {1, 0}, {1, 1}, {2, 0}}}, +}; + +int GRAVITY_LEVEL[MAX_LEVEL+1] = { + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, + //10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 30, 28, 26, 24, 22, 20, 16, 12, 8, 4 +}; + +/******************************************************************************* + Helper Functions for Blocks + *******************************************************************************/ + +void sleep_milli(int milliseconds) +{ + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = milliseconds * 1000 * 1000; + nanosleep(&ts, NULL); +} + +/* + Return the block at the given row and column. + */ +char tg_get(tetris_game *obj, int row, int column) +{ + return obj->board[obj->cols * row + column]; +} + +/* + Set the block at the given row and column. + */ +static void tg_set(tetris_game *obj, int row, int column, char value) +{ + obj->board[obj->cols * row + column] = value; +} + +/* + Check whether a row and column are in bounds. + */ +bool tg_check(tetris_game *obj, int row, int col) +{ + return 0 <= row && row < obj->rows && 0 <= col && col < obj->cols; +} + +/* + Place a block onto the board. + */ +static void tg_put(tetris_game *obj, tetris_block block) +{ + int i; + for (i = 0; i < TETRIS; i++) { + tetris_location cell = TETROMINOS[block.typ][block.ori][i]; + tg_set(obj, block.loc.row + cell.row, block.loc.col + cell.col, + TYPE_TO_CELL(block.typ)); + } +} + +/* + Clear a block out of the board. + */ +static void tg_remove(tetris_game *obj, tetris_block block) +{ + int i; + for (i = 0; i < TETRIS; i++) { + tetris_location cell = TETROMINOS[block.typ][block.ori][i]; + tg_set(obj, block.loc.row + cell.row, block.loc.col + cell.col, TC_EMPTY); + } +} + +/* + Check if a block can be placed on the board. + */ +static bool tg_fits(tetris_game *obj, tetris_block block) +{ + int i, r, c; + for (i = 0; i < TETRIS; i++) { + tetris_location cell = TETROMINOS[block.typ][block.ori][i]; + r = block.loc.row + cell.row; + c = block.loc.col + cell.col; + if (!tg_check(obj, r, c) || TC_IS_FILLED(tg_get(obj, r, c))) { + return false; + } + } + return true; +} + +/* + Return a random tetromino type. + */ +static int random_tetromino(void) { + return rand() % NUM_TETROMINOS; +} + +/* + Create a new falling block and populate the next falling block with a random + one. + */ +static void tg_new_falling(tetris_game *obj) +{ + // Put in a new falling tetromino. + obj->falling = obj->next; + obj->next.typ = random_tetromino(); + obj->next.ori = 0; + obj->next.loc.row = 0; + obj->next.loc.col = obj->cols/2 - 2; +} + +/******************************************************************************* + Game Turn Helpers + *******************************************************************************/ + +/* + Tick gravity, and move the block down if gravity should act. + */ +static void tg_do_gravity_tick(tetris_game *obj) +{ + obj->ticks_till_gravity--; + if (obj->ticks_till_gravity <= 0) { + tg_remove(obj, obj->falling); + obj->falling.loc.row++; + if (tg_fits(obj, obj->falling)) { + obj->ticks_till_gravity = GRAVITY_LEVEL[obj->level]; + } else { + obj->falling.loc.row--; + tg_put(obj, obj->falling); + + tg_new_falling(obj); + } + tg_put(obj, obj->falling); + } +} + +/* + Move the falling tetris block left (-1) or right (+1). + */ +static void tg_move(tetris_game *obj, int direction) +{ + tg_remove(obj, obj->falling); + obj->falling.loc.col += direction; + if (!tg_fits(obj, obj->falling)) { + obj->falling.loc.col -= direction; + } + tg_put(obj, obj->falling); +} + +/* + Send the falling tetris block to the bottom. + */ +static void tg_down(tetris_game *obj) +{ + tg_remove(obj, obj->falling); + while (tg_fits(obj, obj->falling)) { + obj->falling.loc.row++; + } + obj->falling.loc.row--; + tg_put(obj, obj->falling); + tg_new_falling(obj); +} + +/* + Rotate the falling block in either direction (+/-1). + */ +static void tg_rotate(tetris_game *obj, int direction) +{ + tg_remove(obj, obj->falling); + + while (true) { + obj->falling.ori = (obj->falling.ori + direction) % NUM_ORIENTATIONS; + + // If the new orientation fits, we're done. + if (tg_fits(obj, obj->falling)) + break; + + // Otherwise, try moving left to make it fit. + obj->falling.loc.col--; + if (tg_fits(obj, obj->falling)) + break; + + // Finally, try moving right to make it fit. + obj->falling.loc.col += 2; + if (tg_fits(obj, obj->falling)) + break; + + // Put it back in its original location and try the next orientation. + obj->falling.loc.col--; + // Worst case, we come back to the original orientation and it fits, so this + // loop will terminate. + } + + tg_put(obj, obj->falling); +} + +/* + Swap the falling block with the block in the hold buffer. + */ +static void tg_hold(tetris_game *obj) +{ + tg_remove(obj, obj->falling); + if (obj->stored.typ == -1) { + obj->stored = obj->falling; + tg_new_falling(obj); + } else { + int typ = obj->falling.typ, ori = obj->falling.ori; + obj->falling.typ = obj->stored.typ; + obj->falling.ori = obj->stored.ori; + obj->stored.typ = typ; + obj->stored.ori = ori; + while (!tg_fits(obj, obj->falling)) { + obj->falling.loc.row--; + } + } + tg_put(obj, obj->falling); +} + +/* + Perform the action specified by the move. + */ +static void tg_handle_move(tetris_game *obj, tetris_move move) +{ + switch (move) { + case TM_LEFT: + tg_move(obj, -1); + break; + case TM_RIGHT: + tg_move(obj, 1); + break; + case TM_DROP: + tg_down(obj); + break; + case TM_CLOCK: + tg_rotate(obj, 1); + break; + case TM_COUNTER: + tg_rotate(obj, -1); + break; + case TM_HOLD: + tg_hold(obj); + break; + default: + // pass + break; + } +} + +/* + Return true if line i is full. + */ +static bool tg_line_full(tetris_game *obj, int i) +{ + int j; + for (j = 0; j < obj->cols; j++) { + if (TC_IS_EMPTY(tg_get(obj, i, j))) + return false; + } + return true; +} + +/* + Shift every row above r down one. + */ +static void tg_shift_lines(tetris_game *obj, int r) +{ + int i, j; + for (i = r-1; i >= 0; i--) { + for (j = 0; j < obj->cols; j++) { + tg_set(obj, i+1, j, tg_get(obj, i, j)); + tg_set(obj, i, j, TC_EMPTY); + } + } +} + +/* + Find rows that are filled, remove them, shift, and return the number of + cleared rows. + */ +static int tg_check_lines(tetris_game *obj) +{ + int i, nlines = 0; + tg_remove(obj, obj->falling); // don't want to mess up falling block + + for (i = obj->rows-1; i >= 0; i--) { + if (tg_line_full(obj, i)) { + tg_shift_lines(obj, i); + i++; // do this line over again since they're shifted + nlines++; + } + } + + tg_put(obj, obj->falling); // replace + return nlines; +} + +/* + Adjust the score for the game, given how many lines were just cleared. + */ +static void tg_adjust_score(tetris_game *obj, int lines_cleared) +{ + static int line_multiplier[] = {0, 40, 100, 300, 1200}; + obj->points += line_multiplier[lines_cleared] * (obj->level + 1); + if (lines_cleared >= obj->lines_remaining) { + obj->level = MIN(MAX_LEVEL, obj->level + 1); + lines_cleared -= obj->lines_remaining; + obj->lines_remaining = LINES_PER_LEVEL - lines_cleared; + } else { + obj->lines_remaining -= lines_cleared; + } +} + +/* + Return true if the game is over. + */ +static bool tg_game_over(tetris_game *obj) +{ + int i, j; + bool over = false; + tg_remove(obj, obj->falling); + for (i = 0; i < 2; i++) { + for (j = 0; j < obj->cols; j++) { + if (TC_IS_FILLED(tg_get(obj, i, j))) { + over = true; + } + } + } + tg_put(obj, obj->falling); + return over; +} + +/******************************************************************************* + Main Public Functions + *******************************************************************************/ + +/* + Do a single game tick: process gravity, user input, and score. Return true if + the game is still running, false if it is over. + */ +bool tg_tick(tetris_game *obj, tetris_move move) +{ + int lines_cleared; + // Handle gravity. + tg_do_gravity_tick(obj); + + // Handle input. + tg_handle_move(obj, move); + + // Check for cleared lines + lines_cleared = tg_check_lines(obj); + + tg_adjust_score(obj, lines_cleared); + + // Return whether the game will continue (NOT whether it's over) + return !tg_game_over(obj); +} + +void tg_init(tetris_game *obj, int rows, int cols) +{ + // Initialization logic + obj->rows = rows; + obj->cols = cols; + obj->board = malloc(rows * cols); + memset(obj->board, TC_EMPTY, rows * cols); + obj->points = 0; + obj->level = 0; + obj->ticks_till_gravity = GRAVITY_LEVEL[obj->level]; + obj->lines_remaining = LINES_PER_LEVEL; + srand(time(NULL)); + tg_new_falling(obj); + tg_new_falling(obj); + obj->stored.typ = -1; + obj->stored.ori = 0; + obj->stored.loc.row = 0; + obj->next.loc.col = obj->cols/2 - 2; + printf("%d", obj->falling.loc.col); +} + +tetris_game *tg_create(int rows, int cols) +{ + tetris_game *obj = malloc(sizeof(tetris_game)); + tg_init(obj, rows, cols); + return obj; +} + +void tg_destroy(tetris_game *obj) +{ + // Cleanup logic + free(obj->board); +} + +void tg_delete(tetris_game *obj) { + tg_destroy(obj); + free(obj); +} + +/* + Load a game from a file. + */ +tetris_game *tg_load(FILE *f) +{ + tetris_game *obj = malloc(sizeof(tetris_game)); + fread(obj, sizeof(tetris_game), 1, f); + obj->board = malloc(obj->rows * obj->cols); + fread(obj->board, sizeof(char), obj->rows * obj->cols, f); + return obj; +} + +/* + Save a game to a file. + */ +void tg_save(tetris_game *obj, FILE *f) +{ + fwrite(obj, sizeof(tetris_game), 1, f); + fwrite(obj->board, sizeof(char), obj->rows * obj->cols, f); +} + +/* + Print a game board to a file. Really just for early debugging. + */ +void tg_print(tetris_game *obj, FILE *f) { + int i, j; + for (i = 0; i < obj->rows; i++) { + for (j = 0; j < obj->cols; j++) { + if (TC_IS_EMPTY(tg_get(obj, i, j))) { + fputs(TC_EMPTY_STR, f); + } else { + fputs(TC_BLOCK_STR, f); + } + } + fputc('\n', f); + } +} + +/* + 2 columns per cell makes the game much nicer. + */ +#define COLS_PER_CELL 2 +/* + Macro to print a cell of a specific type to a window. + */ +#define ADD_BLOCK(w,x) waddch((w),' '|A_REVERSE|COLOR_PAIR(x)); \ +waddch((w),' '|A_REVERSE|COLOR_PAIR(x)) +#define ADD_EMPTY(w) waddch((w), ' '); waddch((w), ' ') + +/* + Print the tetris board onto the ncurses window. + */ +void display_board(WINDOW *w, tetris_game *obj) +{ + int i, j; + box(w, 0, 0); + for (i = 0; i < obj->rows; i++) { + wmove(w, 1 + i, 1); + for (j = 0; j < obj->cols; j++) { + if (TC_IS_FILLED(tg_get(obj, i, j))) { + ADD_BLOCK(w,tg_get(obj, i, j)); + } else { + ADD_EMPTY(w); + } + } + } + wnoutrefresh(w); +} + +/* + Display a tetris piece in a dedicated window. + */ +void display_piece(WINDOW *w, tetris_block block) +{ + int b; + tetris_location c; + wclear(w); + box(w, 0, 0); + if (block.typ == -1) { + wnoutrefresh(w); + return; + } + for (b = 0; b < TETRIS; b++) { + c = TETROMINOS[block.typ][block.ori][b]; + wmove(w, c.row + 1, c.col * COLS_PER_CELL + 1); + ADD_BLOCK(w, TYPE_TO_CELL(block.typ)); + } + wnoutrefresh(w); +} + +/* + Display score information in a dedicated window. + */ +void display_score(WINDOW *w, tetris_game *tg) +{ + wclear(w); + box(w, 0, 0); + wprintw(w, "Score\n%d\n", tg->points); + wprintw(w, "Level\n%d\n", tg->level); + wprintw(w, "Lines\n%d\n", tg->lines_remaining); + wnoutrefresh(w); +} + +/* + Boss mode! Make it look like you're doing work. + */ +void boss_mode(void) +{ + clear(); + //Mix_PauseMusic(); + printw("user@workstation-312:~/Documents/presentation $ ls -l\n" + "total 528\n" + "drwxr-xr-x 2 user users 4096 Jun 9 17:05 .\n" + "drwxr-xr-x 4 user users 4096 Jun 10 09:52 ..\n" + "-rw-r--r-- 1 user users 88583 Jun 9 14:13 figure1.png\n" + "-rw-r--r-- 1 user users 65357 Jun 9 15:40 figure2.png\n" + "-rw-r--r-- 1 user users 4469 Jun 9 16:17 presentation.aux\n" + "-rw-r--r-- 1 user users 42858 Jun 9 16:17 presentation.log\n" + "-rw-r--r-- 1 user users 2516 Jun 9 16:17 presentation.nav\n" + "-rw-r--r-- 1 user users 183 Jun 9 16:17 presentation.out\n" + "-rw-r--r-- 1 user users 349607 Jun 9 16:17 presentation.pdf\n" + "-rw-r--r-- 1 user users 0 Jun 9 16:17 presentation.snm\n" + "-rw-r--r-- 1 user users 9284 Jun 9 17:05 presentation.tex\n" + "-rw-r--r-- 1 user users 229 Jun 9 16:17 presentation.toc\n" + "\n" + "user@workstation-312:~/Documents/presentation $ "); + echo(); + timeout(-1); + while (getch() != KEY_F(1)); + timeout(0); + noecho(); + clear(); + //Mix_ResumeMusic(); +} + +/* + Save and exit the game. + */ +void save(tetris_game *game, WINDOW *w) +{ + FILE *f; + + wclear(w); + box(w, 0, 0); // return the border + wmove(w, 1, 1); + wprintw(w, "Save and exit? [Y/n] "); + wrefresh(w); + timeout(-1); + if (getch() == 'n') { + timeout(0); + return; + } + f = fopen("tetris.save", "w"); + tg_save(game, f); + fclose(f); + tg_delete(game); + endwin(); + printf("Game saved to \"tetris.save\".\n"); + printf("Resume by passing the filename as an argument to this program.\n"); + exit(EXIT_SUCCESS); +} + +/* + Do the NCURSES initialization steps for color blocks. + */ +void init_colors(void) +{ + start_color(); + //init_color(COLOR_ORANGE, 1000, 647, 0); + init_pair(TC_CELLI, COLOR_CYAN, COLOR_BLACK); + init_pair(TC_CELLJ, COLOR_BLUE, COLOR_BLACK); + init_pair(TC_CELLL, COLOR_WHITE, COLOR_BLACK); + init_pair(TC_CELLO, COLOR_YELLOW, COLOR_BLACK); + init_pair(TC_CELLS, COLOR_GREEN, COLOR_BLACK); + init_pair(TC_CELLT, COLOR_MAGENTA, COLOR_BLACK); + init_pair(TC_CELLZ, COLOR_RED, COLOR_BLACK); +} + +/* + Main tetris game! + */ +#ifdef STANDALONE + +int main(int argc, char **argv) +{ + tetris_game *tg; + tetris_move move = TM_NONE; + bool running = true; + WINDOW *board, *next, *hold, *score; + //Mix_Music *music; + + // Load file if given a filename. + if (argc >= 2) { + FILE *f = fopen(argv[1], "r"); + if (f == NULL) { + perror("tetris"); + exit(EXIT_FAILURE); + } + tg = tg_load(f); + fclose(f); + } else { + // Otherwise create new game. + tg = tg_create(22, 10); + } + + /* Initialize music. + if (SDL_Init(SDL_INIT_AUDIO) < 0) { + fprintf(stderr, "unable to initialize SDL\n"); + exit(EXIT_FAILURE); + } + if (Mix_Init(MIX_INIT_MP3) != MIX_INIT_MP3) { + fprintf(stderr, "unable to initialize SDL_mixer\n"); + exit(EXIT_FAILURE); + } + if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024) != 0) { + fprintf(stderr, "unable to initialize audio\n"); + exit(EXIT_FAILURE); + } + Mix_AllocateChannels(1); // only need background music + music = Mix_LoadMUS("tetris.mp3"); + if (music) { + Mix_PlayMusic(music, -1); + }*/ + + // NCURSES initialization: + initscr(); // initialize curses + cbreak(); // pass key presses to program, but not signals + noecho(); // don't echo key presses to screen + keypad(stdscr, TRUE); // allow arrow keys + timeout(0); // no blocking on getch() + curs_set(0); // set the cursor to invisible + init_colors(); // setup tetris colors + + // Create windows for each section of the interface. + board = newwin(tg->rows + 2, 2 * tg->cols + 2, 0, 0); + next = newwin(6, 10, 0, 2 * (tg->cols + 1) + 1); + hold = newwin(6, 10, 7, 2 * (tg->cols + 1) + 1); + score = newwin(6, 10, 14, 2 * (tg->cols + 1 ) + 1); + + // Game loop + while (running) { + running = tg_tick(tg, move); + display_board(board, tg); + display_piece(next, tg->next); + display_piece(hold, tg->stored); + display_score(score, tg); + doupdate(); + sleep_milli(10); + + switch (getch()) { + case KEY_LEFT: + move = TM_LEFT; + break; + case KEY_RIGHT: + move = TM_RIGHT; + break; + case KEY_UP: + move = TM_CLOCK; + break; + case KEY_DOWN: + move = TM_DROP; + break; + case 'q': + running = false; + move = TM_NONE; + break; + case 'p': + wclear(board); + box(board, 0, 0); + wmove(board, tg->rows/2, (tg->cols*COLS_PER_CELL-6)/2); + wprintw(board, "PAUSED"); + wrefresh(board); + timeout(-1); + getch(); + timeout(0); + move = TM_NONE; + break; + case 'b': + boss_mode(); + move = TM_NONE; + break; + case 's': + save(tg, board); + move = TM_NONE; + break; + case ' ': + move = TM_HOLD; + break; + default: + move = TM_NONE; + } + } + + // Deinitialize NCurses + wclear(stdscr); + endwin(); + + /* Deinitialize Sound + Mix_HaltMusic(); + Mix_FreeMusic(music); + Mix_CloseAudio(); + Mix_Quit();*/ + + // Output ending message. + printf("Game over!\n"); + printf("You finished with %d points on level %d.\n", tg->points, tg->level); + + // Deinitialize Tetris + tg_delete(tg); + return 0; +} +#endif + +/****************************************************************************** + * Copyright © 2014-2019 The SuperNET Developers. * + * * + * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * + * the top-level directory of this distribution for the individual copyright * + * holder information and the developer policies on copyright and licensing. * + * * + * Unless otherwise agreed in a custom licensing agreement, no part of the * + * SuperNET software, including this file may be copied, modified, propagated * + * or distributed except according to the terms contained in the LICENSE file * + * * + * Removal or modification of this copyright notice is prohibited. * + * * + ******************************************************************************/ + +#include "cJSON.h" +#include "CCinclude.h" + +#define TETRIS_REGISTRATION 5 +#define TETRIS_REGISTRATIONSIZE (100 * 10000) +#define TETRIS_MAXPLAYERS 64 // need to send unused fees back to globalCC address to prevent leeching +#define TETRIS_MAXKEYSTROKESGAP 60 +#define TETRIS_MAXITERATIONS 777 + + +std::string Tetris_pname = ""; + +CScript tetris_newgameopret(int64_t buyin,int32_t maxplayers) +{ + CScript opret; uint8_t evalcode = EVAL_TETRIS; + opret << OP_RETURN << E_MARSHAL(ss << evalcode << 'G' << buyin << maxplayers); + return(opret); +} + +CScript tetris_registeropret(uint256 gametxid,uint256 playertxid) +{ + CScript opret; uint8_t evalcode = EVAL_TETRIS; + //fprintf(stderr,"opret.(%s %s).R\n",gametxid.GetHex().c_str(),playertxid.GetHex().c_str()); + opret << OP_RETURN << E_MARSHAL(ss << evalcode << 'R' << gametxid << playertxid); + return(opret); +} + +CScript tetris_keystrokesopret(uint256 gametxid,uint256 batontxid,CPubKey pk,std::vectorkeystrokes) +{ + CScript opret; uint8_t evalcode = EVAL_TETRIS; + opret << OP_RETURN << E_MARSHAL(ss << evalcode << 'K' << gametxid << batontxid << pk << keystrokes); + return(opret); +} + +CScript tetris_highlanderopret(uint8_t funcid,uint256 gametxid,int32_t regslot,CPubKey pk,std::vectorplayerdata,std::string pname) +{ + CScript opret; uint8_t evalcode = EVAL_TETRIS; std::string symbol(ASSETCHAINS_SYMBOL); + opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcid << gametxid << symbol << pname << regslot << pk << playerdata ); + return(opret); +} + +uint8_t tetris_highlanderopretdecode(uint256 &gametxid, uint256 &tokenid, int32_t ®slot, CPubKey &pk, std::vector &playerdata, std::string &symbol, std::string &pname,CScript scriptPubKey) +{ + std::string name, description; std::vector vorigPubkey; + std::vector> oprets, opretsDummy; + std::vector vopretNonfungible, vopret, vopretDummy,origpubkey; + uint8_t e, f,*script; std::vector voutPubkeys; + tokenid = zeroid; + GetOpReturnData(scriptPubKey, vopret); + script = (uint8_t *)vopret.data(); + if ( script[1] == 'c' && (f= DecodeTokenCreateOpRet(scriptPubKey,origpubkey,name,description, oprets)) == 'c' ) + { + GetOpretBlob(oprets, OPRETID_NONFUNGIBLEDATA, vopretNonfungible); + vopret = vopretNonfungible; + } + else if ( script[1] != 'H' && script[1] != 'Q' && (f= DecodeTokenOpRet(scriptPubKey, e, tokenid, voutPubkeys, opretsDummy)) != 0 ) + { + //fprintf(stderr,"decode opret %c tokenid.%s\n",script[1],tokenid.GetHex().c_str()); + GetNonfungibleData(tokenid, vopretNonfungible); //load nonfungible data from the 'tokenbase' tx + vopret = vopretNonfungible; + } + if ( vopret.size() > 2 && E_UNMARSHAL(vopret, ss >> e; ss >> f; ss >> gametxid; ss >> symbol; ss >> pname; ss >> regslot; ss >> pk; ss >> playerdata) != 0 && e == EVAL_TETRIS && (f == 'H' || f == 'Q') ) + { + return(f); + } + fprintf(stderr,"SKIP obsolete: e.%d f.%c game.%s regslot.%d psize.%d\n",e,f,gametxid.GetHex().c_str(),regslot,(int32_t)playerdata.size()); + return(0); +} + +uint8_t tetris_keystrokesopretdecode(uint256 &gametxid,uint256 &batontxid,CPubKey &pk,std::vector &keystrokes,CScript scriptPubKey) +{ + std::vector vopret; uint8_t e,f; + GetOpReturnData(scriptPubKey,vopret); + if ( vopret.size() > 2 && E_UNMARSHAL(vopret,ss >> e; ss >> f; ss >> gametxid; ss >> batontxid; ss >> pk; ss >> keystrokes) != 0 && e == EVAL_TETRIS && f == 'K' ) + { + return(f); + } + return(0); +} + +uint8_t tetris_registeropretdecode(uint256 &gametxid,uint256 &tokenid,uint256 &playertxid,CScript scriptPubKey) +{ + std::string name, description; std::vector vorigPubkey; + std::vector> oprets; + std::vector vopretNonfungible, vopret, vopretDummy,origpubkey; + uint8_t e, f,*script; std::vector voutPubkeys; + tokenid = zeroid; + GetOpReturnData(scriptPubKey, vopret); + script = (uint8_t *)vopret.data(); + if ( script[1] == 'c' && (f= DecodeTokenCreateOpRet(scriptPubKey,origpubkey,name,description,oprets)) == 'c' ) + { + GetOpretBlob(oprets, OPRETID_NONFUNGIBLEDATA, vopretNonfungible); + vopret = vopretNonfungible; + } + else if ( script[1] != 'R' && (f= DecodeTokenOpRet(scriptPubKey, e, tokenid, voutPubkeys, oprets)) != 0 ) + { + GetOpretBlob(oprets, OPRETID_TETRISGAMEDATA, vopretDummy); // blob from non-creation tx opret + vopret = vopretDummy; + } + if ( vopret.size() > 2 && E_UNMARSHAL(vopret,ss >> e; ss >> f; ss >> gametxid; ss >> playertxid) != 0 && e == EVAL_TETRIS && f == 'R' ) + { + return(f); + } + //fprintf(stderr,"e.%d f.%c game.%s playertxid.%s\n",e,f,gametxid.GetHex().c_str(),playertxid.GetHex().c_str()); + return(0); +} + +uint8_t tetris_newgameopreturndecode(int64_t &buyin,int32_t &maxplayers,CScript scriptPubKey) +{ + std::vector vopret; uint8_t e,f; + GetOpReturnData(scriptPubKey,vopret); + if ( vopret.size() > 2 && E_UNMARSHAL(vopret,ss >> e; ss >> f; ss >> buyin; ss >> maxplayers) != 0 && e == EVAL_TETRIS && f == 'G' ) + { + return(f); + } + return(0); +} + +void tetris_univalue(UniValue &result,const char *method,int64_t maxplayers,int64_t buyin) +{ + if ( method != 0 ) + { + result.push_back(Pair("name","tetris")); + result.push_back(Pair("method",method)); + } + if ( maxplayers > 0 ) + result.push_back(Pair("maxplayers",maxplayers)); + if ( buyin >= 0 ) + { + result.push_back(Pair("buyin",ValueFromAmount(buyin))); + if ( buyin == 0 ) + result.push_back(Pair("type","newbie")); + else result.push_back(Pair("type","buyin")); + } +} + +int32_t tetris_iamregistered(int32_t maxplayers,uint256 gametxid,CTransaction tx,char *mytetrisaddr) +{ + int32_t i,vout; uint256 spenttxid,hashBlock; CTransaction spenttx; char destaddr[64]; + for (i=0; i= 0 ) + { + if ( myGetTransaction(spenttxid,spenttx,hashBlock) != 0 && spenttx.vout.size() > 0 ) + { + Getscriptaddress(destaddr,spenttx.vout[0].scriptPubKey); + if ( strcmp(mytetrisaddr,destaddr) == 0 ) + return(1); + //else fprintf(stderr,"myaddr.%s vs %s\n",mytetrisaddr,destaddr); + } //else fprintf(stderr,"cant find spenttxid.%s\n",spenttxid.GetHex().c_str()); + } //else fprintf(stderr,"vout %d is unspent\n",vout); + } + return(0); +} + +int32_t tetris_isvalidgame(struct CCcontract_info *cp,int32_t &gameheight,CTransaction &tx,int64_t &buyin,int32_t &maxplayers,uint256 txid,int32_t unspentv0) +{ + uint256 hashBlock; int32_t i,numvouts; char coinaddr[64]; CPubKey tetrispk; uint64_t txfee = 10000; + buyin = maxplayers = 0; + if ( (txid == zeroid || myGetTransaction(txid,tx,hashBlock) != 0) && (numvouts= tx.vout.size()) > 1 ) + { + if ( txid != zeroid ) + gameheight = komodo_blockheight(hashBlock); + else + { + txid = tx.GetHash(); + //fprintf(stderr,"set txid %s %llu\n",txid.GetHex().c_str(),(long long)CCgettxout(txid,0,1)); + } + if ( IsCClibvout(cp,tx,0,cp->unspendableCCaddr) == txfee && (unspentv0 == 0 || CCgettxout(txid,0,1,0) == txfee) ) + { + if ( tetris_newgameopreturndecode(buyin,maxplayers,tx.vout[numvouts-1].scriptPubKey) == 'G' ) + { + if ( maxplayers < 1 || maxplayers > TETRIS_MAXPLAYERS || buyin < 0 ) + return(-6); + if ( numvouts > 2*maxplayers+1 ) + { + for (i=0; i playerdata,uint256 playertxid,uint256 tokenid,std::string symbol,std::string pname,uint256 gametxid) +{ + int32_t i,vout,spentvini,numvouts,n=0; uint256 txid,spenttxid,hashBlock; struct tetris_player P; char packitemstr[512],*datastr=0; UniValue obj(UniValue::VOBJ),a(UniValue::VARR); CTransaction tx; + memset(&P,0,sizeof(P)); + if ( playerdata.size() > 0 ) + { + datastr = (char *)malloc(playerdata.size()*2+1); + for (i=0; i= 0 ) + txid = spenttxid; + else if ( myIsutxo_spentinmempool(spenttxid,spentvini,txid,vout) == 0 || spenttxid == zeroid ) + { + fprintf(stderr,"mempool tracking error %s/v0\n",txid.ToString().c_str()); + break; + } + txid = spenttxid; + vout = 0; + if ( myGetTransaction(txid,tx,hashBlock) != 0 && (numvouts= tx.vout.size()) > 1 ) + { + for (i=0; i TETRIS_MAXITERATIONS ) + break; + } + obj.push_back(Pair("gametxid",gametxid.GetHex())); + if ( txid != playertxid ) + obj.push_back(Pair("batontxid",txid.GetHex())); + obj.push_back(Pair("playertxid",playertxid.GetHex())); + if ( tokenid != zeroid ) + obj.push_back(Pair("tokenid",tokenid.GetHex())); + else obj.push_back(Pair("tokenid",playertxid.GetHex())); + if ( datastr != 0 ) + { + obj.push_back(Pair("data",datastr)); + free(datastr); + } + obj.push_back(Pair("pack",a)); + obj.push_back(Pair("packsize",(int64_t)P.packsize)); + obj.push_back(Pair("hitpoints",(int64_t)P.hitpoints)); + obj.push_back(Pair("strength",(int64_t)(P.strength&0xffff))); + obj.push_back(Pair("maxstrength",(int64_t)(P.strength>>16))); + obj.push_back(Pair("level",(int64_t)P.level)); + obj.push_back(Pair("experience",(int64_t)P.experience)); + obj.push_back(Pair("dungeonlevel",(int64_t)P.dungeonlevel)); + obj.push_back(Pair("chain",symbol)); + obj.push_back(Pair("pname",pname)); + return(obj); +} + +int32_t tetris_iterateplayer(uint256 ®istertxid,uint256 firsttxid,int32_t firstvout,uint256 lasttxid) // retrace playertxid vins to reach highlander <- this verifies player is valid and tetris_playerdataspend makes sure it can only be used once +{ + uint256 spenttxid,txid = firsttxid; int32_t spentvini,n,vout = firstvout; + registertxid = zeroid; + if ( vout < 0 ) + return(-1); + n = 0; + while ( (spentvini= myIsutxo_spent(spenttxid,txid,vout)) == 0 ) + { + txid = spenttxid; + vout = spentvini; + if ( registertxid == zeroid ) + registertxid = txid; + if ( ++n >= TETRIS_MAXITERATIONS ) + { + fprintf(stderr,"tetris_iterateplayer n.%d, seems something is wrong\n",n); + return(-2); + } + } + if ( txid == lasttxid ) + return(0); + else + { + fprintf(stderr,"firsttxid.%s/v%d -> %s != last.%s\n",firsttxid.ToString().c_str(),firstvout,txid.ToString().c_str(),lasttxid.ToString().c_str()); + return(-1); + } +} + +/* + playertxid is whoever owns the nonfungible satoshi and it might have been bought and sold many times. + highlander is the game winning tx with the player data and is the only place where the unique player data exists + origplayergame is the gametxid that ends up being won by the highlander and they are linked directly as the highlander tx spends gametxid.vout0 + */ + +int32_t tetris_playerdata(struct CCcontract_info *cp,uint256 &origplayergame,uint256 &tokenid,CPubKey &pk,std::vector &playerdata,std::string &symbol,std::string &pname,uint256 playertxid) +{ + uint256 origplayertxid,hashBlock,gametxid,registertxid; CTransaction gametx,playertx,highlandertx; std::vector vopret; uint8_t *script,e,f; int32_t i,regslot,gameheight,numvouts,maxplayers; int64_t buyin; + if ( myGetTransaction(playertxid,playertx,hashBlock) != 0 && (numvouts= playertx.vout.size()) > 0 ) + { + if ( (f= tetris_highlanderopretdecode(gametxid,tokenid,regslot,pk,playerdata,symbol,pname,playertx.vout[numvouts-1].scriptPubKey)) == 'H' || f == 'Q' ) + { + origplayergame = gametxid; + if ( tokenid != zeroid ) + { + playertxid = tokenid; + if ( myGetTransaction(playertxid,playertx,hashBlock) == 0 || (numvouts= playertx.vout.size()) <= 0 ) + { + fprintf(stderr,"couldnt get tokenid.%s\n",playertxid.GetHex().c_str()); + return(-2); + } + } + if ( tetris_isvalidgame(cp,gameheight,gametx,buyin,maxplayers,gametxid,0) == 0 ) + { + //fprintf(stderr,"playertxid.%s got vin.%s/v%d gametxid.%s iterate.%d\n",playertxid.ToString().c_str(),playertx.vin[1].prevout.hash.ToString().c_str(),(int32_t)playertx.vin[1].prevout.n-maxplayers,gametxid.ToString().c_str(),tetris_iterateplayer(registertxid,gametxid,playertx.vin[1].prevout.n-maxplayers,playertxid)); + if ( (tokenid != zeroid || playertx.vin[1].prevout.hash == gametxid) && tetris_iterateplayer(registertxid,gametxid,playertx.vin[1].prevout.n-maxplayers,playertxid) == 0 ) + { + // if registertxid has vin from pk, it can be used + return(0); + } else fprintf(stderr,"hash mismatch or illegal gametxid\n"); + } else fprintf(stderr,"invalid game %s\n",gametxid.GetHex().c_str()); + } //else fprintf(stderr,"invalid player funcid.%c\n",f); + } else fprintf(stderr,"couldnt get playertxid.%s\n",playertxid.GetHex().c_str()); + return(-1); +} + +int32_t tetris_playerdataspend(CMutableTransaction &mtx,uint256 playertxid,int32_t vout,uint256 origplayergame) +{ + int64_t txfee = 10000; CTransaction tx; uint256 hashBlock; + if ( CCgettxout(playertxid,vout,1,0) == 1 ) // not sure if this is enough validation + { + mtx.vin.push_back(CTxIn(playertxid,vout,CScript())); + return(0); + } + else + { + vout = 0; + if ( myGetTransaction(playertxid,tx,hashBlock) != 0 && tx.vout[vout].nValue == 1 && tx.vout[vout].scriptPubKey.IsPayToCryptoCondition() != 0 ) + { + if ( CCgettxout(playertxid,vout,1,0) == 1 ) // not sure if this is enough validation + { + mtx.vin.push_back(CTxIn(playertxid,vout,CScript())); + return(0); + } + } + return(-1); + } +} + +int32_t tetris_findbaton(struct CCcontract_info *cp,uint256 &playertxid,char **keystrokesp,int32_t &numkeys,int32_t ®slot,std::vector &playerdata,uint256 &batontxid,int32_t &batonvout,int64_t &batonvalue,int32_t &batonht,uint256 gametxid,CTransaction gametx,int32_t maxplayers,char *destaddr,int32_t &numplayers,std::string &symbol,std::string &pname) +{ + int32_t i,numvouts,spentvini,n,matches = 0; CPubKey pk; uint256 tid,active,spenttxid,tokenid,hashBlock,txid,origplayergame; CTransaction spenttx,matchtx,batontx; std::vector checkdata; CBlockIndex *pindex; char ccaddr[64],*keystrokes=0; + batonvalue = numkeys = numplayers = batonht = 0; + playertxid = batontxid = zeroid; + for (i=0; i= 0 ) + { + if ( myGetTransaction(spenttxid,spenttx,hashBlock) != 0 && spenttx.vout.size() > 0 ) + { + numplayers++; + Getscriptaddress(ccaddr,spenttx.vout[0].scriptPubKey); + if ( strcmp(destaddr,ccaddr) == 0 ) + { + matches++; + regslot = i; + matchtx = spenttx; + } //else fprintf(stderr,"%d+1 doesnt match %s vs %s\n",i,ccaddr,destaddr); + } //else fprintf(stderr,"%d+1 couldnt find spenttx.%s\n",i,spenttxid.GetHex().c_str()); + } //else fprintf(stderr,"%d+1 unspent\n",i); + } + if ( matches == 1 ) + { + numvouts = matchtx.vout.size(); + //fprintf(stderr,"matchtxid.%s matches.%d numvouts.%d\n",matchtx.GetHash().GetHex().c_str(),matches,numvouts); + if ( tetris_registeropretdecode(txid,tokenid,playertxid,matchtx.vout[numvouts-1].scriptPubKey) == 'R' )//&& txid == gametxid ) + { + //fprintf(stderr,"tokenid.%s txid.%s vs gametxid.%s player.%s\n",tokenid.GetHex().c_str(),txid.GetHex().c_str(),gametxid.GetHex().c_str(),playertxid.GetHex().c_str()); + if ( tokenid != zeroid ) + active = tokenid; + else active = playertxid; + if ( active == zeroid || tetris_playerdata(cp,origplayergame,tid,pk,playerdata,symbol,pname,active) == 0 ) + { + txid = matchtx.GetHash(); + //fprintf(stderr,"scan forward active.%s spenttxid.%s\n",active.GetHex().c_str(),txid.GetHex().c_str()); + n = 0; + while ( CCgettxout(txid,0,1,0) < 0 ) + { + spenttxid = zeroid; + spentvini = -1; + if ( (spentvini= myIsutxo_spent(spenttxid,txid,0)) >= 0 ) + txid = spenttxid; + else + { + if ( myIsutxo_spentinmempool(spenttxid,spentvini,txid,0) == 0 || spenttxid == zeroid ) + { + fprintf(stderr,"mempool tracking error %s/v0\n",txid.ToString().c_str()); + return(-2); + } + } + txid = spenttxid; + //fprintf(stderr,"n.%d next txid.%s/v%d\n",n,txid.GetHex().c_str(),spentvini); + if ( spentvini != 0 ) // game is over? + { + return(0); + } + if ( keystrokesp != 0 && myGetTransaction(spenttxid,spenttx,hashBlock) != 0 && spenttx.vout.size() >= 2 ) + { + uint256 g,b; CPubKey p; std::vector k; + if ( tetris_keystrokesopretdecode(g,b,p,k,spenttx.vout[spenttx.vout.size()-1].scriptPubKey) == 'K' ) + { + keystrokes = (char *)realloc(keystrokes,numkeys + (int32_t)k.size()); + for (i=0; i= TETRIS_MAXITERATIONS ) + { + fprintf(stderr,"tetris_findbaton n.%d, seems something is wrong\n",n); + return(-5); + } + } + //fprintf(stderr,"set baton %s\n",txid.GetHex().c_str()); + batontxid = txid; + batonvout = 0; // not vini + // how to detect timeout, bailedout, highlander + hashBlock = zeroid; + if ( myGetTransaction(batontxid,batontx,hashBlock) != 0 && batontx.vout.size() > 0 ) + { + if ( hashBlock == zeroid ) + batonht = komodo_nextheight(); + else if ( (pindex= komodo_blockindex(hashBlock)) == 0 ) + return(-4); + else batonht = pindex->GetHeight(); + batonvalue = batontx.vout[0].nValue; + //printf("batonht.%d keystrokes[%d]\n",batonht,numkeys); + return(0); + } else fprintf(stderr,"couldnt find baton\n"); + } else fprintf(stderr,"error with playerdata\n"); + } else fprintf(stderr,"findbaton opret error\n"); + } + return(-1); +} + +int32_t tetris_playersalive(int32_t &openslots,int32_t &numplayers,uint256 gametxid,int32_t maxplayers,int32_t gameht,CTransaction gametx) +{ + int32_t i,n,vout,spentvini,registration_open = 0,alive = 0; CTransaction tx; uint256 txid,spenttxid,hashBlock; CBlockIndex *pindex; uint64_t txfee = 10000; + numplayers = openslots = 0; + if ( komodo_nextheight() <= gameht+TETRIS_MAXKEYSTROKESGAP ) + registration_open = 1; + for (i=0; i= 0 ) + txid = spenttxid; + else if ( myIsutxo_spentinmempool(spenttxid,spentvini,txid,vout) == 0 || spenttxid == zeroid ) + { + fprintf(stderr,"mempool tracking error %s/v0\n",txid.ToString().c_str()); + break; + } + txid = spenttxid; + vout = 0; + //fprintf(stderr,"n.%d next txid.%s/v%d\n",n,txid.GetHex().c_str(),spentvini); + if ( spentvini != 0 ) + break; + if ( n++ > TETRIS_MAXITERATIONS ) + break; + } + if ( txid != zeroid ) + { + if ( myGetTransaction(txid,tx,hashBlock) != 0 ) + { + if ( (pindex= komodo_blockindex(hashBlock)) != 0 ) + { + if ( pindex->GetHeight() <= gameht+TETRIS_MAXKEYSTROKESGAP ) + alive++; + } + } + } + } + } + else if ( registration_open != 0 ) + openslots++; + } + //fprintf(stderr,"numalive.%d openslots.%d\n",alive,openslots); + return(alive); +} + +uint64_t tetris_gamefields(UniValue &obj,int64_t maxplayers,int64_t buyin,uint256 gametxid,char *mytetrisaddr) +{ + CBlockIndex *pindex; int32_t ht,openslots,delay,numplayers; uint256 hashBlock; uint64_t seed=0; char cmd[512]; CTransaction tx; + if ( myGetTransaction(gametxid,tx,hashBlock) != 0 && (pindex= komodo_blockindex(hashBlock)) != 0 ) + { + ht = pindex->GetHeight(); + delay = TETRIS_REGISTRATION * (maxplayers > 1); + obj.push_back(Pair("height",ht)); + obj.push_back(Pair("start",ht+delay)); + if ( komodo_nextheight() > ht+delay ) + { + if ( (pindex= komodo_chainactive(ht+delay)) != 0 ) + { + hashBlock = pindex->GetBlockHash(); + obj.push_back(Pair("starthash",hashBlock.ToString())); + memcpy(&seed,&hashBlock,sizeof(seed)); + seed &= (1LL << 62) - 1; + obj.push_back(Pair("seed",(int64_t)seed)); + if ( tetris_iamregistered(maxplayers,gametxid,tx,mytetrisaddr) > 0 ) + sprintf(cmd,"cc/tetris %llu %s",(long long)seed,gametxid.ToString().c_str()); + else sprintf(cmd,"./komodo-cli -ac_name=%s cclib register %d \"[%%22%s%%22]\"",ASSETCHAINS_SYMBOL,EVAL_TETRIS,gametxid.ToString().c_str()); + obj.push_back(Pair("run",cmd)); + } + } + obj.push_back(Pair("alive",tetris_playersalive(openslots,numplayers,gametxid,maxplayers,ht,tx))); + obj.push_back(Pair("openslots",openslots)); + obj.push_back(Pair("numplayers",numplayers)); + } + obj.push_back(Pair("maxplayers",maxplayers)); + obj.push_back(Pair("buyin",ValueFromAmount(buyin))); + return(seed); +} + +void tetris_gameplayerinfo(struct CCcontract_info *cp,UniValue &obj,uint256 gametxid,CTransaction gametx,int32_t vout,int32_t maxplayers,char *mytetrisaddr) +{ + // identify if bailout or quit or timed out + uint256 batontxid,spenttxid,gtxid,ptxid,tokenid,hashBlock,playertxid; CTransaction spenttx,batontx; int32_t numplayers,regslot,numkeys,batonvout,batonht,retval; int64_t batonvalue; std::vector playerdata; char destaddr[64]; std::string symbol,pname; + destaddr[0] = 0; + if ( myIsutxo_spent(spenttxid,gametxid,vout) >= 0 ) + { + if ( myGetTransaction(spenttxid,spenttx,hashBlock) != 0 && spenttx.vout.size() > 0 ) + Getscriptaddress(destaddr,spenttx.vout[0].scriptPubKey); + } + obj.push_back(Pair("slot",(int64_t)vout-1)); + if ( (retval= tetris_findbaton(cp,playertxid,0,numkeys,regslot,playerdata,batontxid,batonvout,batonvalue,batonht,gametxid,gametx,maxplayers,destaddr,numplayers,symbol,pname)) == 0 ) + { + if ( CCgettxout(gametxid,maxplayers+vout,1,0) == 10000 ) + { + if ( myGetTransaction(batontxid,batontx,hashBlock) != 0 && batontx.vout.size() > 1 ) + { + if ( tetris_registeropretdecode(gtxid,tokenid,ptxid,batontx.vout[batontx.vout.size()-1].scriptPubKey) == 'R' && ptxid == playertxid && gtxid == gametxid ) + obj.push_back(Pair("status","registered")); + else obj.push_back(Pair("status","alive")); + } else obj.push_back(Pair("status","error")); + } else obj.push_back(Pair("status","finished")); + obj.push_back(Pair("baton",batontxid.ToString())); + obj.push_back(Pair("tokenid",tokenid.ToString())); + obj.push_back(Pair("batonaddr",destaddr)); + obj.push_back(Pair("ismine",strcmp(mytetrisaddr,destaddr)==0)); + obj.push_back(Pair("batonvout",(int64_t)batonvout)); + obj.push_back(Pair("batonvalue",ValueFromAmount(batonvalue))); + obj.push_back(Pair("batonht",(int64_t)batonht)); + if ( playerdata.size() > 0 ) + obj.push_back(Pair("player",tetris_playerobj(playerdata,playertxid,tokenid,symbol,pname,gametxid))); + } else fprintf(stderr,"findbaton err.%d\n",retval); +} + +int64_t tetris_registrationbaton(CMutableTransaction &mtx,uint256 gametxid,CTransaction gametx,int32_t maxplayers) +{ + int32_t vout,j,r; int64_t nValue; + if ( gametx.vout.size() > maxplayers+1 ) + { + r = rand() % maxplayers; + for (j=0; j 0 ) + { + result.push_back(Pair("hex",rawtx)); + if ( DecodeHexTx(tx,rawtx) != 0 ) + { + if ( broadcastflag != 0 && myAddtomempool(tx) != 0 ) + RelayTransaction(tx); + result.push_back(Pair("txid",tx.GetHash().ToString())); + result.push_back(Pair("result","success")); + } else result.push_back(Pair("error","decode hex")); + } else result.push_back(Pair("error","couldnt finalize CCtx")); + return(result); +} + +UniValue tetris_newgame(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) +{ + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + UniValue result(UniValue::VOBJ); std::string rawtx; CPubKey tetrispk,mypk; char *jsonstr; uint64_t inputsum,change,required,buyin=0; int32_t i,n,maxplayers = 1; + if ( txfee == 0 ) + txfee = 10000; + if ( params != 0 && (n= cJSON_GetArraySize(params)) > 0 ) + { + if ( n > 0 ) + { + maxplayers = juint(jitem(params,0),0); + if ( n > 1 ) + buyin = jdouble(jitem(params,1),0) * COIN + 0.0000000049; + } + } + if ( maxplayers < 1 || maxplayers > TETRIS_MAXPLAYERS ) + return(cclib_error(result,"illegal maxplayers")); + mypk = pubkey2pk(Mypubkey()); + tetrispk = GetUnspendable(cp,0); + tetris_univalue(result,"newgame",maxplayers,buyin); + required = (3*txfee + maxplayers*(TETRIS_REGISTRATIONSIZE+txfee)); + if ( (inputsum= AddCClibInputs(cp,mtx,tetrispk,required,16,cp->unspendableCCaddr)) >= required ) + { + mtx.vout.push_back(MakeCC1vout(cp->evalcode,txfee,tetrispk)); // for highlander TCBOO creation + for (i=0; ievalcode,TETRIS_REGISTRATIONSIZE,tetrispk,tetrispk)); + for (i=0; ievalcode,txfee,tetrispk,tetrispk)); + if ( (change= inputsum - required) >= txfee ) + mtx.vout.push_back(MakeCC1vout(cp->evalcode,change,tetrispk)); + rawtx = FinalizeCCTx(0,cp,mtx,mypk,txfee,tetris_newgameopret(buyin,maxplayers)); + return(tetris_rawtxresult(result,rawtx,1)); + } + else return(cclib_error(result,"illegal maxplayers")); + return(result); +} + +UniValue tetris_playerinfo(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) +{ + UniValue result(UniValue::VOBJ); std::vector playerdata; uint256 playertxid,tokenid,origplayergame;int32_t n; CPubKey pk; bits256 t; std::string symbol,pname; + result.push_back(Pair("result","success")); + tetris_univalue(result,"playerinfo",-1,-1); + if ( params != 0 && (n= cJSON_GetArraySize(params)) > 0 ) + { + if ( n > 0 ) + { + playertxid = juint256(jitem(params,0)); + if ( tetris_playerdata(cp,origplayergame,tokenid,pk,playerdata,symbol,pname,playertxid) < 0 ) + return(cclib_error(result,"invalid playerdata")); + result.push_back(Pair("player",tetris_playerobj(playerdata,playertxid,tokenid,symbol,pname,origplayergame))); + } else return(cclib_error(result,"no playertxid")); + return(result); + } else return(cclib_error(result,"couldnt reparse params")); +} + +UniValue tetris_register(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) +{ + // vin0 -> TETRIS_REGISTRATIONSIZE 1of2 registration baton from creategame + // vin1 -> optional nonfungible character vout @ + // vin2 -> original creation TCBOO playerdata used + // vin3+ -> buyin + // vout0 -> keystrokes/completion baton + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + UniValue result(UniValue::VOBJ); char destaddr[64],coinaddr[64]; uint256 tokenid,gametxid,origplayergame,playertxid,hashBlock; int32_t err,maxplayers,gameheight,n,numvouts,vout=1; int64_t inputsum,buyin,CCchange=0; CPubKey pk,mypk,tetrispk,burnpk; CTransaction tx,playertx; std::vector playerdata; std::string rawtx,symbol,pname; bits256 t; + + if ( txfee == 0 ) + txfee = 10000; + mypk = pubkey2pk(Mypubkey()); + burnpk = pubkey2pk(ParseHex(CC_BURNPUBKEY)); + tetrispk = GetUnspendable(cp,0); + tetris_univalue(result,"register",-1,-1); + playertxid = tokenid = zeroid; + if ( params != 0 && (n= cJSON_GetArraySize(params)) > 0 ) + { + if ( n > 0 ) + { + gametxid = juint256(jitem(params,0)); + if ( (err= tetris_isvalidgame(cp,gameheight,tx,buyin,maxplayers,gametxid,1)) == 0 ) + { + if ( n > 1 ) + { + playertxid = juint256(jitem(params,1)); + if ( tetris_playerdata(cp,origplayergame,tokenid,pk,playerdata,symbol,pname,playertxid) < 0 ) + return(cclib_error(result,"couldnt extract valid playerdata")); + if ( tokenid != zeroid ) // if it is tokentransfer this will be 0 + vout = 1; + } + if ( komodo_nextheight() > gameheight + TETRIS_MAXKEYSTROKESGAP ) + return(cclib_error(result,"didnt register in time, TETRIS_MAXKEYSTROKESGAP")); + tetris_univalue(result,0,maxplayers,buyin); + GetCCaddress1of2(cp,coinaddr,tetrispk,mypk); + if ( tetris_iamregistered(maxplayers,gametxid,tx,coinaddr) > 0 ) + return(cclib_error(result,"already registered")); + if ( (inputsum= tetris_registrationbaton(mtx,gametxid,tx,maxplayers)) != TETRIS_REGISTRATIONSIZE ) + return(cclib_error(result,"couldnt find available registration baton")); + else if ( playertxid != zeroid && tetris_playerdataspend(mtx,playertxid,vout,origplayergame) < 0 ) + return(cclib_error(result,"couldnt find playerdata to spend")); + else if ( buyin > 0 && AddNormalinputs(mtx,mypk,buyin,64) < buyin ) + return(cclib_error(result,"couldnt find enough normal funds for buyin")); + if ( tokenid != zeroid ) + { + mtx.vin.push_back(CTxIn(tokenid,0)); // spending cc marker as token is burned + char unspendableTokenAddr[64]; uint8_t tokenpriv[32]; struct CCcontract_info *cpTokens, tokensC; + cpTokens = CCinit(&tokensC, EVAL_TOKENS); + CPubKey unspPk = GetUnspendable(cpTokens, tokenpriv); + GetCCaddress(cpTokens, unspendableTokenAddr, unspPk); + CCaddr2set(cp, EVAL_TOKENS, unspPk, tokenpriv, unspendableTokenAddr); + } + mtx.vout.push_back(MakeCC1of2vout(cp->evalcode,buyin + inputsum - txfee,tetrispk,mypk)); + GetCCaddress1of2(cp,destaddr,tetrispk,tetrispk); + CCaddr1of2set(cp,tetrispk,tetrispk,cp->CCpriv,destaddr); + mtx.vout.push_back(MakeTokensCC1vout(cp->evalcode, 1, burnpk)); + + uint8_t e, funcid; uint256 tid; std::vector voutPubkeys, voutPubkeysEmpty; int32_t didtx = 0; + CScript opretRegister = tetris_registeropret(gametxid, playertxid); + if ( playertxid != zeroid ) + { + voutPubkeysEmpty.push_back(burnpk); + if ( myGetTransaction(playertxid,playertx,hashBlock) != 0 ) + { + std::vector> oprets; + if ( (funcid= DecodeTokenOpRet(playertx.vout.back().scriptPubKey, e, tid, voutPubkeys, oprets)) != 0) + { // if token in the opret + didtx = 1; + if ( funcid == 'c' ) + tid = tokenid == zeroid ? playertxid : tokenid; + vscript_t vopretRegister; + GetOpReturnData(opretRegister, vopretRegister); + rawtx = FinalizeCCTx(0, cp, mtx, mypk, txfee, + EncodeTokenOpRet(tid, voutPubkeysEmpty /*=never spent*/, std::make_pair(OPRETID_TETRISGAMEDATA, vopretRegister))); + } + } + } + if ( didtx == 0 ) + rawtx = FinalizeCCTx(0, cp, mtx, mypk, txfee, opretRegister); + + return(tetris_rawtxresult(result,rawtx,1)); + } else return(cclib_error(result,"invalid gametxid")); + } else return(cclib_error(result,"no gametxid")); + } else return(cclib_error(result,"couldnt reparse params")); +} + +UniValue tetris_keystrokes(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) +{ + // vin0 -> baton from registration or previous keystrokes + // vout0 -> new baton + // opret -> user input chars + // being killed should auto broadcast (possible to be suppressed?) + // respawn to be prevented by including timestamps + int32_t nextheight = komodo_nextheight(); + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(),nextheight); + UniValue result(UniValue::VOBJ); CPubKey tetrispk,mypk; uint256 gametxid,playertxid,batontxid; int64_t batonvalue,buyin; std::vector keystrokes,playerdata; int32_t numplayers,regslot,numkeys,batonht,batonvout,n,elapsed,gameheight,maxplayers; CTransaction tx; CTxOut txout; char *keystrokestr,destaddr[64]; std::string rawtx,symbol,pname; bits256 t; uint8_t mypriv[32]; + if ( txfee == 0 ) + txfee = 10000; + tetris_univalue(result,"keystrokes",-1,-1); + if ( params != 0 && (n= cJSON_GetArraySize(params)) == 2 && (keystrokestr= jstr(jitem(params,1),0)) != 0 ) + { + gametxid = juint256(jitem(params,0)); + result.push_back(Pair("gametxid",gametxid.GetHex())); + result.push_back(Pair("keystrokes",keystrokestr)); + keystrokes = ParseHex(keystrokestr); + mypk = pubkey2pk(Mypubkey()); + tetrispk = GetUnspendable(cp,0); + GetCCaddress1of2(cp,destaddr,tetrispk,mypk); + if ( tetris_isvalidgame(cp,gameheight,tx,buyin,maxplayers,gametxid,1) == 0 ) + { + if ( tetris_findbaton(cp,playertxid,0,numkeys,regslot,playerdata,batontxid,batonvout,batonvalue,batonht,gametxid,tx,maxplayers,destaddr,numplayers,symbol,pname) == 0 ) + { + result.push_back(Pair("batontxid",batontxid.GetHex())); + result.push_back(Pair("playertxid",playertxid.GetHex())); + if ( maxplayers == 1 || nextheight <= batonht+TETRIS_MAXKEYSTROKESGAP ) + { + mtx.vin.push_back(CTxIn(batontxid,batonvout,CScript())); //this validates user if pk + mtx.vout.push_back(MakeCC1of2vout(cp->evalcode,batonvalue-txfee,tetrispk,mypk)); + Myprivkey(mypriv); + CCaddr1of2set(cp,tetrispk,mypk,mypriv,destaddr); + rawtx = FinalizeCCTx(0,cp,mtx,mypk,txfee,tetris_keystrokesopret(gametxid,batontxid,mypk,keystrokes)); + //fprintf(stderr,"KEYSTROKES.(%s)\n",rawtx.c_str()); + return(tetris_rawtxresult(result,rawtx,1)); + } else return(cclib_error(result,"keystrokes tx was too late")); + } else return(cclib_error(result,"couldnt find batontxid")); + } else return(cclib_error(result,"invalid gametxid")); + } else return(cclib_error(result,"couldnt reparse params")); +} + +char *tetris_extractgame(int32_t makefiles,char *str,int32_t *numkeysp,std::vector &newdata,uint64_t &seed,uint256 &playertxid,struct CCcontract_info *cp,uint256 gametxid,char *tetrisaddr) +{ + CPubKey tetrispk; int32_t i,num,retval,maxplayers,gameheight,batonht,batonvout,numplayers,regslot,numkeys,err; std::string symbol,pname; CTransaction gametx; int64_t buyin,batonvalue; char fname[64],*keystrokes = 0; std::vector playerdata; uint256 batontxid; FILE *fp; uint8_t newplayer[10000]; struct tetris_player P,endP; + tetrispk = GetUnspendable(cp,0); + *numkeysp = 0; + seed = 0; + num = numkeys = 0; + playertxid = zeroid; + str[0] = 0; + if ( (err= tetris_isvalidgame(cp,gameheight,gametx,buyin,maxplayers,gametxid,0)) == 0 ) + { + if ( (retval= tetris_findbaton(cp,playertxid,&keystrokes,numkeys,regslot,playerdata,batontxid,batonvout,batonvalue,batonht,gametxid,gametx,maxplayers,tetrisaddr,numplayers,symbol,pname)) == 0 ) + { + UniValue obj; + seed = tetris_gamefields(obj,maxplayers,buyin,gametxid,tetrisaddr); + //fprintf(stderr,"(%s) found baton %s numkeys.%d seed.%llu playerdata.%d playertxid.%s\n",pname.size()!=0?pname.c_str():Tetris_pname.c_str(),batontxid.ToString().c_str(),numkeys,(long long)seed,(int32_t)playerdata.size(),playertxid.GetHex().c_str()); + memset(&P,0,sizeof(P)); + if ( playerdata.size() > 0 ) + { + for (i=0; i no playerdata\n"); + newdata.resize(0); + *numkeysp = numkeys; + return(keystrokes); + /* P.gold = (P.gold * 8) / 10; + if ( keystrokes != 0 ) + { + free(keystrokes); + keystrokes = 0; + *numkeysp = 0; + return(keystrokes); + }*/ + } + else + { + sprintf(str,"$$$gold.%d hp.%d strength.%d/%d level.%d exp.%d dl.%d",endP.gold,endP.hitpoints,endP.strength&0xffff,endP.strength>>16,endP.level,endP.experience,endP.dungeonlevel); + //fprintf(stderr,"%s\n",str); + *numkeysp = numkeys; + return(keystrokes); + } + } else num = 0; + } + else + { + fprintf(stderr,"extractgame: couldnt find baton keystrokes.%p retval.%d\n",keystrokes,retval); + if ( keystrokes != 0 ) + free(keystrokes), keystrokes = 0; + } + } else fprintf(stderr,"extractgame: invalid game\n"); + //fprintf(stderr,"extract %s\n",gametxid.GetHex().c_str()); + return(0); +} + +UniValue tetris_extract(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) +{ + UniValue result(UniValue::VOBJ); CPubKey pk,tetrispk; int32_t i,n,numkeys,flag = 0; uint64_t seed; char str[512],tetrisaddr[64],*pubstr,*hexstr,*keystrokes = 0; std::vector newdata; uint256 gametxid,playertxid; FILE *fp; uint8_t pub33[33]; + pk = pubkey2pk(Mypubkey()); + tetrispk = GetUnspendable(cp,0); + result.push_back(Pair("name","tetris")); + result.push_back(Pair("method","extract")); + tetrisaddr[0] = 0; + if ( params != 0 && (n= cJSON_GetArraySize(params)) > 0 ) + { + if ( n > 0 ) + { + gametxid = juint256(jitem(params,0)); + result.push_back(Pair("gametxid",gametxid.GetHex())); + if ( n == 2 ) + { + if ( (pubstr= jstr(jitem(params,1),0)) != 0 ) + { + if (strlen(pubstr) == 66 ) + { + decode_hex(pub33,33,pubstr); + pk = buf2pk(pub33); + } + else if ( strlen(pubstr) < 36 ) + strcpy(tetrisaddr,pubstr); + } + //fprintf(stderr,"gametxid.%s %s\n",gametxid.GetHex().c_str(),pubstr); + } + if ( tetrisaddr[0] == 0 ) + GetCCaddress1of2(cp,tetrisaddr,tetrispk,pk); + result.push_back(Pair("tetrisaddr",tetrisaddr)); + str[0] = 0; + if ( (keystrokes= tetris_extractgame(1,str,&numkeys,newdata,seed,playertxid,cp,gametxid,tetrisaddr)) != 0 ) + { + result.push_back(Pair("status","success")); + flag = 1; + hexstr = (char *)malloc(numkeys*2 + 1); + for (i=0; i playerdata,uint256 gametxid,CPubKey pk) +{ + static uint32_t good,bad; static uint256 prevgame; + char str[512],*keystrokes,tetrisaddr[64],str2[67],fname[64]; int32_t i,dungeonlevel,numkeys; std::vector newdata; uint64_t seed,mult = 10; CPubKey tetrispk; struct tetris_player P; + *cashoutp = 0; + tetrispk = GetUnspendable(cp,0); + GetCCaddress1of2(cp,tetrisaddr,tetrispk,pk); + //fprintf(stderr,"call extractgame\n"); + if ( (keystrokes= tetris_extractgame(0,str,&numkeys,newdata,seed,playertxid,cp,gametxid,tetrisaddr)) != 0 ) + { + //fprintf(stderr,"numkeys.%d tetris_extractgame %s\n",numkeys,gametxid.GetHex().c_str()); + free(keystrokes); + sprintf(fname,"tetris.%llu.pack",(long long)seed); + remove(fname); + + //fprintf(stderr,"extracted.(%s)\n",str); + for (i=0; i no playerdata, good.%d bad.%d\n",good,bad); + } + *cashoutp = 0; + return(0); + } + } + if ( gametxid != prevgame ) + { + prevgame = gametxid; + bad++; + fprintf(stderr,"%s playerdata: gold.%d hp.%d strength.%d/%d level.%d exp.%d dl.%d\n",gametxid.GetHex().c_str(),P.gold,P.hitpoints,P.strength&0xffff,P.strength>>16,P.level,P.experience,P.dungeonlevel); + fprintf(stderr,"newdata[%d] != playerdata[%d], numkeys.%d %s pub.%s playertxid.%s good.%d bad.%d\n",(int32_t)newdata.size(),(int32_t)playerdata.size(),numkeys,tetrisaddr,pubkey33_str(str2,(uint8_t *)&pk),playertxid.GetHex().c_str(),good,bad); + } + } + sprintf(fname,"tetris.%llu.pack",(long long)seed); + remove(fname); + //fprintf(stderr,"no keys tetris_extractgame %s\n",gametxid.GetHex().c_str()); + return(-1); +} + +UniValue tetris_finishgame(uint64_t txfee,struct CCcontract_info *cp,cJSON *params,char *method) +{ + //vin0 -> highlander vout from creategame TCBOO + //vin1 -> keystrokes baton of completed game, must be last to quit or first to win, only spent registration batons matter. If more than 60 blocks since last keystrokes, it is forfeit + //vins2+ -> rest of unspent registration utxo so all newgame vouts are spent + //vout0 -> nonfungible character with pack @ + //vout1 -> 1% ingame gold and all the buyins + + // detect if last to bailout + // vin0 -> kestrokes baton of completed game with Q + // vout0 -> playerdata marker + // vout0 -> 1% ingame gold + // get any playerdata, get all keystrokes, replay game and compare final state + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + UniValue result(UniValue::VOBJ); std::string rawtx,symbol,pname; CTransaction gametx; uint64_t seed,mult; int64_t buyin,batonvalue,inputsum,cashout,CCchange=0; int32_t i,err,gameheight,tmp,numplayers,regslot,n,num,dungeonlevel,numkeys,maxplayers,batonht,batonvout; char mytetrisaddr[64],*keystrokes = 0; std::vector playerdata,newdata,nodata; uint256 batontxid,playertxid,gametxid; CPubKey mypk,tetrispk; uint8_t player[10000],mypriv[32],funcid; + struct CCcontract_info *cpTokens, tokensC; + + if ( txfee == 0 ) + txfee = 10000; + mypk = pubkey2pk(Mypubkey()); + tetrispk = GetUnspendable(cp,0); + GetCCaddress1of2(cp,mytetrisaddr,tetrispk,mypk); + result.push_back(Pair("name","tetris")); + result.push_back(Pair("method",method)); + result.push_back(Pair("mytetrisaddr",mytetrisaddr)); + if ( strcmp(method,"bailout") == 0 ) + { + funcid = 'Q'; + mult = 10; //100000; + } + else + { + funcid = 'H'; + mult = 20; //200000; + } + if ( params != 0 && (n= cJSON_GetArraySize(params)) > 0 ) + { + if ( n > 0 ) + { + gametxid = juint256(jitem(params,0)); + result.push_back(Pair("gametxid",gametxid.GetHex())); + if ( (err= tetris_isvalidgame(cp,gameheight,gametx,buyin,maxplayers,gametxid,1)) == 0 ) + { + if ( tetris_findbaton(cp,playertxid,&keystrokes,numkeys,regslot,playerdata,batontxid,batonvout,batonvalue,batonht,gametxid,gametx,maxplayers,mytetrisaddr,numplayers,symbol,pname) == 0 ) + { + UniValue obj; struct tetris_player P; + seed = tetris_gamefields(obj,maxplayers,buyin,gametxid,mytetrisaddr); + fprintf(stderr,"(%s) found baton %s numkeys.%d seed.%llu playerdata.%d\n",pname.size()!=0?pname.c_str():Tetris_pname.c_str(),batontxid.ToString().c_str(),numkeys,(long long)seed,(int32_t)playerdata.size()); + memset(&P,0,sizeof(P)); + if ( playerdata.size() > 0 ) + { + for (i=0; i 0 ) + { + newdata.resize(num); + for (i=0; i no playerdata\n"); + newdata.resize(0); + //P.gold = (P.gold * 8) / 10; + } + else + { + cpTokens = CCinit(&tokensC, EVAL_TOKENS); + mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, txfee, GetUnspendable(cpTokens,NULL))); // marker to token cc addr, burnable and validated + mtx.vout.push_back(MakeTokensCC1vout(cp->evalcode,1,mypk)); + if ( P.amulet != 0 ) + mult *= 5; + dungeonlevel = P.dungeonlevel; + if ( P.amulet != 0 && dungeonlevel < 21 ) + dungeonlevel = 21; + cashout = (uint64_t)P.gold * P.gold * mult * dungeonlevel; + fprintf(stderr,"\nextracted $$$gold.%d -> %.8f TETRIS hp.%d strength.%d/%d level.%d exp.%d dl.%d n.%d amulet.%d\n",P.gold,(double)cashout/COIN,P.hitpoints,P.strength&0xffff,P.strength>>16,P.level,P.experience,P.dungeonlevel,n,P.amulet); + if ( funcid == 'H' && maxplayers > 1 ) + { + if ( P.amulet == 0 ) + { + if ( numplayers != maxplayers ) + return(cclib_error(result,"numplayers != maxplayers")); + else if ( tetris_playersalive(tmp,tmp,gametxid,maxplayers,gameheight,gametx) > 1 ) + return(cclib_error(result,"highlander must be a winner or last one standing")); + } + cashout += numplayers * buyin; + } + if ( cashout >= txfee ) + { + if ( (inputsum= AddCClibInputs(cp,mtx,tetrispk,cashout,16,cp->unspendableCCaddr)) > (uint64_t)P.gold*mult ) + CCchange = (inputsum - cashout); + mtx.vout.push_back(CTxOut(cashout,CScript() << ParseHex(HexStr(mypk)) << OP_CHECKSIG)); + } + } + } + mtx.vout.push_back(MakeCC1vout(cp->evalcode,CCchange + (batonvalue-3*txfee),tetrispk)); + Myprivkey(mypriv); + CCaddr1of2set(cp,tetrispk,mypk,mypriv,mytetrisaddr); + CScript opret; + if ( pname.size() == 0 ) + pname = Tetris_pname; + if ( newdata.size() == 0 ) + { + opret = tetris_highlanderopret(funcid, gametxid, regslot, mypk, nodata,pname); + rawtx = FinalizeCCTx(0,cp,mtx,mypk,txfee,opret); + //fprintf(stderr,"nodata finalizetx.(%s)\n",rawtx.c_str()); + } + else + { + opret = tetris_highlanderopret(funcid, gametxid, regslot, mypk, newdata,pname); + char seedstr[32]; + sprintf(seedstr,"%llu",(long long)seed); + std::vector vopretNonfungible; + GetOpReturnData(opret, vopretNonfungible); + rawtx = FinalizeCCTx(0, cp, mtx, mypk, txfee, EncodeTokenCreateOpRet('c', Mypubkey(), std::string(seedstr), gametxid.GetHex(), vopretNonfungible)); + } + return(tetris_rawtxresult(result,rawtx,1)); + } + result.push_back(Pair("result","success")); + } else fprintf(stderr,"illegal game err.%d\n",err); + } else fprintf(stderr,"parameters only n.%d\n",n); + } + return(result); +} + +UniValue tetris_bailout(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) +{ + return(tetris_finishgame(txfee,cp,params,"bailout")); +} + +UniValue tetris_highlander(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) +{ + return(tetris_finishgame(txfee,cp,params,"highlander")); +} + +UniValue tetris_gameinfo(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) +{ + UniValue result(UniValue::VOBJ),a(UniValue::VARR); int32_t i,n,gameheight,maxplayers,numvouts; uint256 txid; CTransaction tx; int64_t buyin; uint64_t seed; bits256 t; char mytetrisaddr[64],str[64]; CPubKey mypk,tetrispk; + result.push_back(Pair("name","tetris")); + result.push_back(Pair("method","gameinfo")); + if ( params != 0 && (n= cJSON_GetArraySize(params)) > 0 ) + { + if ( n > 0 ) + { + txid = juint256(jitem(params,0)); + result.push_back(Pair("gametxid",txid.GetHex())); + if ( tetris_isvalidgame(cp,gameheight,tx,buyin,maxplayers,txid,0) == 0 ) + { + result.push_back(Pair("result","success")); + result.push_back(Pair("gameheight",(int64_t)gameheight)); + mypk = pubkey2pk(Mypubkey()); + tetrispk = GetUnspendable(cp,0); + GetCCaddress1of2(cp,mytetrisaddr,tetrispk,mypk); + //fprintf(stderr,"mytetrisaddr.%s\n",mytetrisaddr); + seed = tetris_gamefields(result,maxplayers,buyin,txid,mytetrisaddr); + result.push_back(Pair("seed",(int64_t)seed)); + for (i=0; i > unspentOutputs; + tetrispk = GetUnspendable(cp,0); + GetCCaddress(cp,coinaddr,tetrispk); + SetCCunspents(unspentOutputs,coinaddr); + nextheight = komodo_nextheight(); + for (std::vector >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++) + { + txid = it->first.txhash; + vout = (int32_t)it->first.index; + //char str[65]; fprintf(stderr,"%s check %s/v%d %.8f\n",coinaddr,uint256_str(str,txid),vout,(double)it->second.satoshis/COIN); + if ( it->second.satoshis != txfee || vout != 0 ) // reject any that are not highlander markers + continue; + if ( tetris_isvalidgame(cp,gameheight,tx,buyin,maxplayers,txid,1) == 0 && nextheight <= gameheight+TETRIS_MAXKEYSTROKESGAP ) + { + tetris_playersalive(openslots,numplayers,txid,maxplayers,gameheight,tx); + if ( openslots > 0 ) + a.push_back(txid.GetHex()); + } + } + result.push_back(Pair("result","success")); + tetris_univalue(result,"pending",-1,-1); + result.push_back(Pair("pending",a)); + result.push_back(Pair("numpending",(int64_t)a.size())); + return(result); +} + +UniValue tetris_players(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) +{ + UniValue result(UniValue::VOBJ),a(UniValue::VARR); int64_t buyin; uint256 tokenid,gametxid,txid,hashBlock; CTransaction playertx,tx; int32_t maxplayers,vout,numvouts; std::vector playerdata; CPubKey tetrispk,mypk,pk; std::string symbol,pname; char coinaddr[64]; + std::vector > unspentOutputs; + tetrispk = GetUnspendable(cp,0); + mypk = pubkey2pk(Mypubkey()); + GetTokensCCaddress(cp,coinaddr,mypk); + SetCCunspents(unspentOutputs,coinaddr); + tetris_univalue(result,"players",-1,-1); + for (std::vector >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++) + { + txid = it->first.txhash; + vout = (int32_t)it->first.index; + //char str[65]; fprintf(stderr,"%s check %s/v%d %.8f\n",coinaddr,uint256_str(str,txid),vout,(double)it->second.satoshis/COIN); + if ( it->second.satoshis != 1 || vout > 1 ) + continue; + if ( tetris_playerdata(cp,gametxid,tokenid,pk,playerdata,symbol,pname,txid) == 0 )//&& pk == mypk ) + { + a.push_back(txid.GetHex()); + //a.push_back(Pair("playerdata",tetris_playerobj(playerdata))); + } + } + result.push_back(Pair("playerdata",a)); + result.push_back(Pair("numplayerdata",(int64_t)a.size())); + return(result); +} + +UniValue tetris_games(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) +{ + UniValue result(UniValue::VOBJ),a(UniValue::VARR),b(UniValue::VARR); uint256 txid,hashBlock,gametxid,tokenid,playertxid; int32_t vout,maxplayers,gameheight,numvouts; CPubKey tetrispk,mypk; char coinaddr[64]; CTransaction tx,gametx; int64_t buyin; + std::vector > addressIndex; + //std::vector > unspentOutputs; + tetrispk = GetUnspendable(cp,0); + mypk = pubkey2pk(Mypubkey()); + GetCCaddress1of2(cp,coinaddr,tetrispk,mypk); + //SetCCunspents(unspentOutputs,coinaddr); + SetCCtxids(addressIndex,coinaddr); + tetris_univalue(result,"games",-1,-1); + for (std::vector >::const_iterator it=addressIndex.begin(); it!=addressIndex.end(); it++) + //for (std::vector >::const_iterator it=unspentOutputs.begin(); it!=unspentOutputs.end(); it++) + { + txid = it->first.txhash; + vout = (int32_t)it->first.index; + //char str[65]; fprintf(stderr,"%s check %s/v%d %.8f\n",coinaddr,uint256_str(str,txid),vout,(double)it->second.satoshis/COIN); + if ( vout == 0 ) + { + if ( myGetTransaction(txid,tx,hashBlock) != 0 && (numvouts= tx.vout.size()) > 1 ) + { + if ( tetris_registeropretdecode(gametxid,tokenid,playertxid,tx.vout[numvouts-1].scriptPubKey) == 'R' ) + { + if ( tetris_isvalidgame(cp,gameheight,gametx,buyin,maxplayers,gametxid,0) == 0 ) + { + if ( CCgettxout(txid,vout,1,0) < 0 ) + b.push_back(gametxid.GetHex()); + else a.push_back(gametxid.GetHex()); + } + } + } + } + } + result.push_back(Pair("pastgames",b)); + result.push_back(Pair("games",a)); + result.push_back(Pair("numgames",(int64_t)(a.size()+b.size()))); + return(result); +} + +UniValue tetris_setname(uint64_t txfee,struct CCcontract_info *cp,cJSON *params) +{ + UniValue result(UniValue::VOBJ); int32_t n; char *namestr = 0; + tetris_univalue(result,"setname",-1,-1); + if ( params != 0 && (n= cJSON_GetArraySize(params)) > 0 ) + { + if ( n > 0 ) + { + if ( (namestr= jstri(params,0)) != 0 ) + { + result.push_back(Pair("result","success")); + result.push_back(Pair("pname",namestr)); + tetris_pname = namestr; + return(result); + } + } + } + result.push_back(Pair("result","error")); + result.push_back(Pair("error","couldnt get name")); + return(result); +} + +bool tetris_validate(struct CCcontract_info *cp,int32_t height,Eval *eval,const CTransaction tx) +{ + CScript scriptPubKey; std::vector vopret; uint8_t *script,e,f,funcid,tokentx=0; int32_t i,maxplayers,enabled = 0,decoded=0,regslot,ind,err,dispflag,gameheight,score,numvouts; CTransaction vintx,gametx; CPubKey pk; uint256 hashBlock,gametxid,txid,tokenid,batontxid,playertxid,ptxid; int64_t buyin,cashout; std::vector playerdata,keystrokes; std::string symbol,pname; + if ( strcmp(ASSETCHAINS_SYMBOL,"ROGUE") == 0 ) + { + if (height < 21274 ) + return(true); + else if ( height > 50000 ) + enabled = 1; + } else enabled = 1; + if ( (numvouts= tx.vout.size()) > 1 ) + { + txid = tx.GetHash(); + scriptPubKey = tx.vout[numvouts-1].scriptPubKey; + GetOpReturnData(scriptPubKey,vopret); + if ( vopret.size() > 2 ) + { + script = (uint8_t *)vopret.data(); + funcid = script[1]; + if ( (e= script[0]) == EVAL_TOKENS ) + { + tokentx = funcid; + if ( (funcid= tetris_highlanderopretdecode(gametxid,tokenid,regslot,pk,playerdata,symbol,pname,scriptPubKey)) == 0 ) + { + if ( (funcid= tetris_registeropretdecode(gametxid,tokenid,playertxid,scriptPubKey)) == 0 ) + { + fprintf(stderr,"ht.%d couldnt decode tokens opret (%c)\n",height,script[1]); + } else e = EVAL_TETRIS, decoded = 1; + } else e = EVAL_TETRIS, decoded = 1; + } + if ( e == EVAL_TETRIS ) + { + //fprintf(stderr,"ht.%d tetris.(%c)\n",height,script[1]); + if ( decoded == 0 ) + { + switch ( funcid ) + { + case 'G': // seems just need to make sure no vout abuse is left to do + gametx = tx; + gametxid = tx.GetHash(); + gameheight = height; + if ( (err= tetris_isvalidgame(cp,gameheight,gametx,buyin,maxplayers,zeroid,0)) != 0 ) + { + fprintf(stderr,"height.%d %s tetris_isvalidgame error.%d\n",height,gametxid.GetHex().c_str(),err); + return eval->Invalid("invalid gametxid"); + } + //fprintf(stderr,"height.%d %s tetris_isvalidgame\n",height,gametxid.GetHex().c_str()); + return(true); + break; + case 'R': + if ( (funcid= tetris_registeropretdecode(gametxid,tokenid,playertxid,scriptPubKey)) != 'R' ) + { + return eval->Invalid("couldnt decode register opret"); + } + // baton is created + // validation is done below + break; + case 'K': + if ( (funcid= tetris_keystrokesopretdecode(gametxid,batontxid,pk,keystrokes,scriptPubKey)) != 'K' ) + { + return eval->Invalid("couldnt decode keystrokes opret"); + } + // spending the baton proves it is the user if the pk is the signer + return(true); + break; + case 'H': case 'Q': + // done in the next switch statement as there are some H/Q tx with playerdata which would skip this section + break; + default: + return eval->Invalid("illegal tetris non-decoded funcid"); + break; + } + } + switch ( funcid ) + { + case 'R': // register + // verify vout amounts are as they should be and no vins that shouldnt be + return(true); + case 'H': // win + case 'Q': // bailout + if ( (f= tetris_highlanderopretdecode(gametxid,tokenid,regslot,pk,playerdata,symbol,pname,scriptPubKey)) != funcid ) + { + //fprintf(stderr,"height.%d couldnt decode H/Q opret\n",height); + //if ( height > 20000 ) + return eval->Invalid("couldnt decode H/Q opret"); + } + // verify pk belongs to this tx + if ( tokentx == 'c' && playerdata.size() > 0 ) + { + static char laststr[512]; char cashstr[512]; + if ( tetris_playerdata_validate(&cashout,ptxid,cp,playerdata,gametxid,pk) < 0 ) + { + sprintf(cashstr,"tokentx.(%c) decoded.%d ht.%d gametxid.%s player.%s invalid playerdata[%d]\n",tokentx,decoded,height,gametxid.GetHex().c_str(),ptxid.GetHex().c_str(),(int32_t)playerdata.size()); + if ( strcmp(laststr,cashstr) != 0 ) + { + strcpy(laststr,cashstr); + fprintf(stderr,"%s\n",cashstr); + } + if ( enabled != 0 ) + return eval->Invalid("mismatched playerdata"); + } + if ( funcid == 'H' ) + cashout *= 2; + sprintf(cashstr,"tokentx.(%c) decoded.%d ht.%d txid.%s %.8f vs vout2 %.8f",tokentx,decoded,height,txid.GetHex().c_str(),(double)cashout/COIN,(double)tx.vout[2].nValue/COIN); + if ( strcmp(laststr,cashstr) != 0 ) + { + strcpy(laststr,cashstr); + fprintf(stderr,"%s\n",cashstr); + } + if ( enabled != 0 && tx.vout[2].nValue != cashout ) + return eval->Invalid("mismatched cashout amount"); + } + if ( funcid == 'Q' ) + { + // verify vin/vout + } + else // 'H' + { + // verify vin/vout and proper payouts + } + return(true); + break; + default: + return eval->Invalid("illegal tetris funcid"); + break; + } + } else return eval->Invalid("illegal evalcode"); + } else return eval->Invalid("opret too small"); + } else return eval->Invalid("not enough vouts"); + return(true); +} +