处理:如何修复计时器显示以及如何在游戏结束时显示时间分数?

Processing: How to fix timer display and how to display time score at end of game?

我正在研究一个升序拼图,记录用户完成拼图所花费的时间,一旦拼图完成,它会将用户带到一个游戏结束屏幕,显示用户他们的时间。我的问题是显示的时间重叠并且没有按应有的方式更新。我也不确定如何节省用户完成拼图所花费的时间。

final int NUM_SQUARES = 4;
int[][] board = new int[NUM_SQUARES][NUM_SQUARES];
int sqSide;
Timer startTimer;

void setup(){
  size(500, 500);
  setupGame();
  sqSide = width/NUM_SQUARES;
}

void setupGame(){
  sqSide = width/NUM_SQUARES;
  startTimer = new Timer(0);
  //populate the board
    //generate random number
    //check if we have it already inside the array
    //if we have, then go on to generate another random number
    //if we do not have it, then we can store inside array
  for(int row=0; row<NUM_SQUARES; row++){
    for(int col=0; col<NUM_SQUARES; col++){
      int randVal;
      do{
        randVal = int(random(1, NUM_SQUARES*NUM_SQUARES+1) );
      }while( searchFor(randVal) );
      board[row][col] = randVal;
    }
  }
  
  //visual representation of the board
  for(int row=0; row<NUM_SQUARES; row++){
    for(int col=0; col<NUM_SQUARES; col++){
      fill(random(0,255), random(0,255), random(0,255));
      rect(col*sqSide, row*sqSide, sqSide, sqSide);
      fill(0);
      textSize(30);
      text(board[row][col], (col+0.5)*sqSide, (row+0.5)*sqSide);
    }
  }
}

class Timer{
  float Time;
  
  Timer(float set){
    Time = set;
  }
  float getTime(){
    return(Time);
  }
  void setTime(float set){
     Time = set;
  }
  void countUP(){
    Time += 1/frameRate;
  }
}


boolean searchFor(int itemToBeSearched){
  for(int i=0; i<NUM_SQUARES; i++){
    for(int j=0; j<NUM_SQUARES; j++){
      if(itemToBeSearched == board[i][j]){
        return true;
      }
    }
  }
  return false;
}

void draw(){
  startTimer.countUP();
  fill(0);
  text(startTimer.getTime(), 20,20);
}

int clickedRow, clickedCol, releasedRow, releasedCol;

void mousePressed(){
  clickedRow = int(mouseY/sqSide);
  clickedCol = int(mouseX/sqSide);
}
void mouseReleased(){
  releasedRow = int(mouseY/sqSide);
  releasedCol = int(mouseX/sqSide);
  //swap
  int buffer = board[clickedRow][clickedCol];
  board[clickedRow][clickedCol] = board[releasedRow][releasedCol];
  board[releasedRow][releasedCol] = buffer;
  
  //visual representation - finish up
  //show what is inside board[clickedRow][clikedCol]
  //then show what is inside board[releasedRow][releasedCol]
  //where the child pressed
  
  fill(random(0,255), random(0,255), random(0,255));
  rect(clickedCol*sqSide, clickedRow*sqSide, sqSide, sqSide);
  fill(0);
  text(board[clickedRow][clickedCol],(clickedCol+0.5)*sqSide, (clickedRow+0.5)*sqSide) ;
  
  //where the child released
  fill(random(0,255), random(0,255), random(0,255));
  rect(releasedCol*sqSide, releasedRow*sqSide, sqSide, sqSide);
  fill(0);
  text(board[releasedRow][releasedCol],(releasedCol+0.5)*sqSide, (releasedRow+0.5)*sqSide);
  
  
  if(gameOver()==true){ //calling function gameOver
    background(255);
    String s = "Congratulations!"; 
    String d = "Click to start again!";
    fill(0);
    text(s, 125, 225);
    text(d, 125, 250);
    if(mousePressed == true){
      setupGame();
    }
  }
}

//definition of gameOver
boolean gameOver(){
  int counter=1;
  for(int row=0; row<NUM_SQUARES; row++){
    for(int col=0; col<NUM_SQUARES; col++){
      if(board[row][col] !=counter){
        return false;
      }
      counter++;
    }
  }
  return true;
}

那里有很多好的工作,尤其是在管理板/交换元素等方面。需要重点关注它。

看起来在绘图方面也有点混乱。 您可以在 draw() 中绘制一次元素;像现在一样在顶部渲染重叠元素,但是您需要注意需要渲染一次或多次的内容。

计时器文本留下痕迹,因为背景永远不会被清除,但棋盘只绘制一次。您将需要重新绘制 background/board 以修复文本,但某些代码位需要移动或需要以不同方式处理。

例如:

fill(random(0,255), random(0,255), random(0,255));

如果你在 draw() 中调用它,颜色会不断变化,这是你不想要的。

但是,您可以在 setup() 中生成一次颜色,然后在 draw() 中 re-use 生成它们,而无需重置值。

鼠标事件和何时或应该在哪个状态下重置似乎也有些混淆。

考虑到上述注释,这是您的代码的 re-organised 版本:

final int NUM_SQUARES = 4;
int[][] board       = new int[NUM_SQUARES][NUM_SQUARES];
// also remember the colours set once in setup() to re-use in draw()
int[][] boardColors = new int[NUM_SQUARES][NUM_SQUARES];
int sqSide;
Timer startTimer;

int clickedRow, clickedCol, releasedRow, releasedCol;
// this will store the result of gameOver() once so it can be re-used
boolean isGameOver;

void setup(){
  size(500, 500);
  setupGame();
}

void setupGame(){
  sqSide = width/NUM_SQUARES;
  startTimer = new Timer(0);
  clearBoard();
  //populate the board
  //generate random number
  //check if we have it already inside the array
  //if we have, then go on to generate another random number
  //if we do not have it, then we can store inside array
  shuffleBoard();
}

void clearBoard(){
  for(int row = 0; row < NUM_SQUARES; row++){
    for(int col = 0; col < NUM_SQUARES; col++){
      board[row][col] = 0;
    }
  }
}

void shuffleBoard(){
  for(int row = 0; row < NUM_SQUARES; row++){
    for(int col = 0; col < NUM_SQUARES; col++){
      int randVal;
      do{
        randVal = int(random(1, NUM_SQUARES*NUM_SQUARES+1) );
      }while( searchFor(randVal) );
      board[row][col] = randVal;
      boardColors[row][col] = color(random(0,255), random(0,255), random(0,255));
    }
  }
}

void drawBoard(){
  //visual representation of the board
  for(int row = 0; row < NUM_SQUARES; row++){
    for(int col = 0; col < NUM_SQUARES; col++){
      fill(boardColors[row][col]);
      rect(col * sqSide, row * sqSide, sqSide, sqSide);
      fill(255);
      textSize(30);
      text(board[row][col], (col + 0.5) * sqSide, (row + 0.5) * sqSide);
    }
  }
}

boolean searchFor(int itemToBeSearched){
  for(int i = 0; i < NUM_SQUARES; i++){
    for(int j = 0; j < NUM_SQUARES; j++){
      if(itemToBeSearched == board[i][j]){
        return true;
      }
    }
  }
  return false;
}

void draw(){
  background(255);
  // draw based on state
  if(gameOver()){
    // render game over state
    String s = "Congratulations!"; 
    String d = "Click to start again!";
    fill(0);
    text(s, 125, 225);
    text(d, 125, 250);
    // reset game only if clicking in game over state 
    if(mousePressed == true){
      setupGame();
    }
  }else{
    // render game state
    startTimer.countUp();
    // re-render board 
    drawBoard();
    // render text on top
    fill(0);
    text(startTimer.getTime(), 20, 30);
  }
}



void mousePressed(){
  clickedRow = int(mouseY/sqSide);
  clickedCol = int(mouseX/sqSide);
}

void mouseReleased(){
  releasedRow = int(mouseY/sqSide);
  releasedCol = int(mouseX/sqSide);
  //swap
  int buffer = board[clickedRow][clickedCol];
  board[clickedRow][clickedCol] = board[releasedRow][releasedCol];
  board[releasedRow][releasedCol] = buffer;
}

//definition of gameOver
boolean gameOver(){
  int counter=1;
  for(int row=0; row<NUM_SQUARES; row++){
    for(int col=0; col<NUM_SQUARES; col++){
      if(board[row][col] !=counter){
        return false;
      }
      counter++;
    }
  }
  return true;
}

class Timer{
  float time;
  
  Timer(float set){
    time = set;
  }
  float getTime(){
    return(time);
  }
  void setTime(float set){
     time = set;
  }
  void countUp(){
    time += 1 / frameRate;
  }
}

它可能会更容易组织一个基本的 finite state machine(FSM)。 这听起来比实际更复杂:基本思想是隔离每个状态的功能并严格处理状态之间的转换,从而允许您相应地 reset/update 数据。例如,一个游戏运行状态将只需要处理与游戏玩法相关的数据,直到该状态完成。同样,游戏结束状态只处理它的数据,直到它完成(当用户点击时)。有多种写法。

这里有一个围绕处理草图如何运行的建议:

  • setup() 是在开始时设置一次的地方
  • draw() 是事情发生的地方updated/rendered
  • 鼠标/键/等事件处理用户输入

您已经在使用 class (Timer) 这将非常相似:

// the two states
StateDisplay playState;
StateDisplay gameOverState;
// a reference to the current state
StateDisplay currentState;

void setup(){
  size(500, 500);
  textAlign(CENTER);
  // setup each state
  playState = new PlayState();
  gameOverState = new GameOverState();
  // set the reference to the 1st state
  currentState = playState;
}

// forward events to the current state: doesn't matter if it's play or game over, they're can all handle it
void draw(){
  currentState.draw();
}

void mousePressed(){
  currentState.mousePressed();
}

void mouseReleased(){
  currentState.mouseReleased();
}

// global function to be called by each state as it exits
// this does the state switch: reset state data as required
void onStateExit(StateDisplay exitingState){
  // game play state to game over state
  if(exitingState == playState){
    // cast each state to access specialised variables
    // in this case store the last timer value to display on the game over screen
    ((GameOverState)gameOverState).timerValue = ((PlayState)playState).startTimer.getTimeFormatted();
    // set the state
    currentState = gameOverState;
  }
  // game over state to game play state
  if(exitingState == gameOverState){
    // reset game
    playState.setup();
    // set the state
    currentState = playState;
  }
}

class Timer{
  float time;
  
  Timer(float set){
    time = set;
  }
  
  float getTime(){
    return time;
  }
  
  String getTimeFormatted(){
    return nfc(time, 2);
  }
  
  void setTime(float set){
     time = set;
  }
  
  void countUp(){
    time += 1 / frameRate;
  }
}

// "parent" state class that all other states will extend
// and inherit functionality from
class StateDisplay{
  
  StateDisplay(){
    // as soon as the constructor is called, set up this state
    this.setup();
  }
  
  void setup(){
    // to be overriden by subclass
  }
  
  void draw(){  
    // to be overriden by subclass 
  }
    
  
  void mousePressed(){
    // to be overriden by subclass
  }
  
  void mouseReleased(){
    // to be overriden by subclass
  }

}

class PlayState extends StateDisplay{
  
  final int NUM_SQUARES = 4;
  int[][] board;
  int[][] boardColors;
  int sqSide;
  Timer startTimer;
  
  int clickedRow, clickedCol, releasedRow, releasedCol;
  
  void setup(){
    setupGame();
  }
  
  void setupGame(){
    this.board       = new int[NUM_SQUARES][NUM_SQUARES];
    this.boardColors = new int[NUM_SQUARES][NUM_SQUARES];
    
    sqSide = width/NUM_SQUARES;
    startTimer = new Timer(0);
    //populate the board
    //generate random number
    //check if we have it already inside the array
    //if we have, then go on to generate another random number
    //if we do not have it, then we can store inside array
    for(int row = 0; row < NUM_SQUARES; row++){
      for(int col = 0; col < NUM_SQUARES; col++){
        int randVal;
        do{
          randVal = int(random(1, NUM_SQUARES*NUM_SQUARES+1) );
        }while( searchFor(randVal) );
        board[row][col] = randVal;
        boardColors[row][col] = color(random(0,255), random(0,255), random(0,255));
      }
    }
  }
  
  
  boolean searchFor(int itemToBeSearched){
    for(int i = 0; i < NUM_SQUARES; i++){
      for(int j = 0; j < NUM_SQUARES; j++){
        if(itemToBeSearched == board[i][j]){
          return true;
        }
      }
    }
    return false;
  }
  
  //definition of gameOver
  boolean gameOver(){
    int counter=1;
    for(int row=0; row<NUM_SQUARES; row++){
      for(int col=0; col<NUM_SQUARES; col++){
        if(board[row][col] !=counter){
          return false;
        }
        counter++;
      }
    }
    return true;
  }


  
  void drawBoard(){
    //visual representation of the board
    for(int row = 0; row < NUM_SQUARES; row++){
      for(int col = 0; col < NUM_SQUARES; col++){
        fill(boardColors[row][col]);
        rect(col * sqSide, row*sqSide, sqSide, sqSide);
        fill(255);
        textSize(30);
        text(board[row][col], (col+0.5) * sqSide, (row + 0.5) * sqSide);
      }
    }
  }
  
  void draw(){
    // clear screen
    background(255);
    // redraw board
    drawBoard();
    // optional: drag and drop visual cue
    if(mousePressed){
      fill(255, 192);
      text(board[clickedRow][clickedCol], mouseX, mouseY);
    }
    
    startTimer.countUp();
    fill(255);
    text(startTimer.getTimeFormatted()+"s", width * 0.5, 30);
  }
  
  void mousePressed(){
    clickedRow = int(mouseY/sqSide);
    clickedCol = int(mouseX/sqSide);
  }
  
  void mouseReleased(){
    releasedRow = int(mouseY/sqSide);
    releasedCol = int(mouseX/sqSide);
    //swap
    int buffer = board[clickedRow][clickedCol];
    board[clickedRow][clickedCol] = board[releasedRow][releasedCol];
    board[releasedRow][releasedCol] = buffer;
    
    if(gameOver()){
      onStateExit(this);
    }
  }
  
}

class GameOverState extends StateDisplay{
  // time taken to solve
  String timerValue = "";

  void draw(){
    String s = "Congratulations!\n" +
               "Solved in " + timerValue + "s\n" + 
               "Click to start again!";
    background(255);
    fill(0);
    text(s, width * 0.5, 225);
  }
  
  void mouseReleased(){
    onStateExit(this);
  }
  
}

代码可能看起来很冗长,但基本上所做的大部分工作都是按状态对功能进行分组:就好像您是 运行 mini-sketches 并在它们之间交换。

可能是新的东西是:

  • 扩展一个class:扩展(subclass)的class将从父class继承properties/methods(superclass)
  • polymorphism:依靠 subclasses 与 superclass 的共同点来对待它们,就好像它们是一样的(尽管每个内部的实现是不同的) ). (例如,所有州都有 draw(),但每个州都呈现其他内容)
  • casting:在这种特殊情况下,允许 subclass 表现得像它自己(所有它都是额外的 properties/methods),而不仅仅是从 superclass 共享的东西.更具体地说,在上面的代码中,计时器数据从游戏进行状态传递到游戏结束状态以显示。

尽管以这种方式进行设置需要做更多的工作,但保持状态隔离并使用相同的逻辑轻松添加更多状态(例如高分稳定、开始/游戏菜单屏幕等)可能是值得的.).将每个状态作为 min-sketch 您还可以使用选项卡将其组织起来,从而节省一直上下滚动的时间: