All it did was keep around compressed concatenation of the opponents plays, and a compressed concatenation of opponent and my plays interleaved. When it was my turn to play, I would extend the strings with the three possible one turn plays, i.e. extend by 'r', 'p', 's' and re-compress, whichever gave the shortest compression was my guess of what the opponent would play next. Then I would play whatever defeated opponent's guessed play.
The whole thing was 15~20 lines but of course I was standing on shoulder of giants and the std library. The prediction part was entirely abstracted / co-opted out to the compressor. Better compressors would yield better results. Lempel-Ziv worked well enough as long as there were enough rounds.
The theory for this can run quite deep. There are algorithms that will do the best possible if the opponent is using a finite state m/c, which computers are anyway. The performance of these are determined by the state space of the FSM. If one wants to go deeper then one would land in the territory of Kolomogorov complexity.